文章目录

  • 前言
    • 效果
      • 主页面
      • 消息提示
      • 聊天页面
      • 登录注册
  • 前端
    • 项目构建
      • 依赖
      • 项目结构
      • 登录注册
        • 验证码部分
        • 登录页面
        • 注册页面
    • 主页面
      • 流程
      • websocket
      • loadmessage
      • 消息发送
    • 完整代码
  • 后端
    • 环境
    • 配置
    • 数据库设计
    • Dao层及其mapper
    • Dao相关的服务
    • 登录注册实现
      • 交互信息
      • 登录注册
      • token生产解析
      • 拦截器
    • 聊天室
      • 进入聊天室
      • 聊天部分
      • 完整代码
    • 聊天信息加载
  • 总结

前言

失踪人口回归兄弟们,差不多连续5天没有发布博文了,许久不见。当然也是最近确实不在状态而且比较忙,所以就没有去更新博文,加上最近作业时真的多,各种大作业顶不住。

那么今天也是给大家展示一个小dome,基于SpringBoot + mybatis + websocket 做的在线聊天器。本来是要做在线群聊的,但是这个前端我是真的不想调了,本来是想嫖的页面的,结果怎么说,还是自己做吧,可控。

代码也比较简单,后端搭建写起来其实也就两三个小时,主要是前端,那玩意零零散散花了两天,然后还有这个调试,总体代码从星期一开始写,到星期三写完了,后面没空不想写,于是拖到今天调测完,砍了不少功能。

ok,我们先来看看这个效果图哈。

效果

主页面

消息提示

聊天页面


大概就这样,当然还有登录,注册。

登录注册

大概的话就是这个样子。

OK,现在呢,咱们就进入到这个代码阶段。

前端

首先我们先看到前端。

项目构建

既然是从0开始,那么我们就从o开始说。咱们这个是使用vue2 + elementui 写的,为什么是vue2,简单,我没升级嘛。能跑就行,我也不是专门写前端的。

首先省略 使用 vue-cli 开始项目哈。

依赖

我这里就说我这里使用到的依赖。

elementUI
axios
websocket

就这几个主要的。

项目结构

这里其实就三个页面。

登录注册

在此之前,我们先来看看这个路由设计哈。

 routes: [{path: '/',name: 'mian',component: main},{path: '/login',name: 'login',component: login},{path: '/register',name: 'register',component: register}],mode: "history"

就非常的简单哈。

验证码部分

在这里我们先来说说,这个验证码部分吧。
这个呢,是由前端生成的。是这个组件在干活


<template><div class="s-canvas"><canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas></div>
</template><script>
export default {name: "SIdentify",props: {identifyCode: {type: String,default: '1234'},fontSizeMin: {type: Number,default: 25},fontSizeMax: {type: Number,default: 30},backgroundColorMin: {type: Number,default: 255},backgroundColorMax: {type: Number,default: 255},colorMin: {type: Number,default: 0},colorMax: {type: Number,default: 160},lineColorMin: {type: Number,default: 100},lineColorMax: {type: Number,default: 255},dotColorMin: {type: Number,default: 0},dotColorMax: {type: Number,default: 255},contentWidth: {type: Number,default: 112},contentHeight: {type: Number,default: 31}},methods: {// 生成一个随机数randomNum(min, max) {return Math.floor(Math.random() * (max - min) + min)},// 生成一个随机的颜色randomColor(min, max) {let r = this.randomNum(min, max)let g = this.randomNum(min, max)let b = this.randomNum(min, max)return 'rgb(' + r + ',' + g + ',' + b + ')'},drawPic() {let canvas = document.getElementById('s-canvas')let ctx = canvas.getContext('2d')ctx.textBaseline = 'bottom'// 绘制背景ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)// 绘制文字for (let i = 0; i < this.identifyCode.length; i++) {this.drawText(ctx, this.identifyCode[i], i)}this.drawLine(ctx)this.drawDot(ctx)},drawText(ctx, txt, i) {ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)var deg = this.randomNum(-45, 45)// 修改坐标原点和旋转角度ctx.translate(x, y)ctx.rotate(deg * Math.PI / 180)ctx.fillText(txt, 0, 0)// 恢复坐标原点和旋转角度ctx.rotate(-deg * Math.PI / 180)ctx.translate(-x, -y)},drawLine(ctx) {// 绘制干扰线for (let i = 0; i < 5; i++) {ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)ctx.beginPath()ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))ctx.stroke()}},drawDot(ctx) {// 绘制干扰点for (let i = 0; i < 80; i++) {ctx.fillStyle = this.randomColor(0, 255)ctx.beginPath()ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)ctx.fill()}}},watch: {identifyCode() {this.drawPic()}},mounted() {this.drawPic()}
}
</script><style scoped>
.s-canvas {height: 38px;}
.s-canvas canvas{margin-top: 1px;margin-left: 8px;
}
</style>

之后就是引用,这个是很简单的。

登录页面

在开始之前,我还要说一下这个代理转发,这个代理我是直接在前端做的,我的原则是压力给到前端~

ok ,到这里咱们就能够放代码了

<template><div><el-form :model="formLogin" :rules="rules" ref="ruleForm" label-width="0px" class="login-bok"><el-form-item prop="account"><el-input v-model="formLogin.account" placeholder="账号"><i slot="prepend" class="el-icon-s-custom"/></el-input></el-form-item><el-form-item prop="password"><el-input type="password" placeholder="密码" v-model="formLogin.password"><i slot="prepend" class="el-icon-lock"/></el-input></el-form-item><el-form-item prop="code"><el-row :span="24"><el-col :span="12"><el-input v-model="formLogin.code" auto-complete="off" placeholder="请输入验证码" size=""></el-input></el-col><el-col :span="12"><div class="login-code" @click="refreshCode"><!--验证码组件--><s-identify :identifyCode="identifyCode"></s-identify></div></el-col></el-row></el-form-item><el-form-item><div class="login-btn"><el-button type="primary" @click="goregist()" style="margin-left: auto;width: 35%" >注册</el-button><el-button type="primary" @click="submitForm()" style="margin-left: 27%;width: 35%">登录</el-button></div></el-form-item></el-form></div>
</template><script>
import SIdentify from "../../components/SIdentify";export default {name: "login",components: { SIdentify },data() {return{formLogin: {account: "",password: "",code: "",token: '',success: '',},identifyCodes: '1234567890abcdefjhijklinopqrsduvwxyz',//随机串内容identifyCode: '',// 校验rules: {account:[{ required: true, message: "请输入用户名", trigger: "blur" }],password: [{ required: true, message: "请输入密码", trigger: "blur" }],code: [{ required: true, message: "请输入验证码", trigger: "blur" }]}}},mounted () {// 初始化验证码this.identifyCode = ''this.makeCode(this.identifyCodes, 4)},methods:{refreshCode () {this.identifyCode = ''this.makeCode(this.identifyCodes, 4)},makeCode (o, l) {for (let i = 0; i < l; i++) {this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]}},randomNum (min, max) {return Math.floor(Math.random() * (max - min) + min)},goregist(){this.$router.push("/register")},logincount(){this.axios({url: "/boot/login",method: 'post',headers: { "type": "hello" },data: {account:  this.formLogin.account,password: this.formLogin.password.toLowerCase(),}}).then(res =>{this.formLogin.success = res.data.successthis.formLogin.token = res.data.tokenif(this.formLogin.success =='1'){//设置token七天过期localStorage.setExpire("token",this.formLogin.token,604800000);alert("登录成功~")this.$router.push("/")}else {alert("用户名或密码错误!")}})},submitForm(){if (this.formLogin.code.toLowerCase() !== this.identifyCode.toLowerCase()) {this.$message.error('请填写正确验证码')this.refreshCode()}else {this.logincount()}}},}
</script><style scoped>
.login-bok{width: 30%;margin: 150px auto;border: 1px solid #DCDFE6;padding: 20px;border-radius: 10px;box-shadow: 0 0 30px #DCDFE6;
}
</style>

注册页面

<template><div><el-form :model="formRegist" :rules="rules" ref="ruleForm" label-width="0px" class="login-bok"><el-form-item prop="account"><el-input v-model="formRegist.account" placeholder="创建账号" :maxlength="16" ><i slot="prepend" class="el-icon-s-custom"/></el-input></el-form-item><el-form-item prop="username"><el-input v-model="formRegist.username" placeholder="创建用户" :maxlength="16" ><i slot="prepend" class="el-icon-s-custom"/></el-input></el-form-item><el-form-item prop="password"><el-input type="password"  placeholder="输入密码" :maxlength="16"  v-model="formRegist.password"><i slot="prepend" class="el-icon-lock"/></el-input></el-form-item><el-form-item prop="againpassword"><el-input type="password" placeholder="再次输入密码" :maxlength="16"  v-model="formRegist.againpassword"><i slot="prepend" class="el-icon-lock"/></el-input></el-form-item><el-form-item prop="code"><el-row :span="24"><el-col :span="12"><el-input v-model="formRegist.code" auto-complete="off" placeholder="请输入验证码" size=""></el-input></el-col><el-col :span="12"><div class="login-code" @click="refreshCode"><!--验证码组件--><s-identify :identifyCode="identifyCode"></s-identify></div></el-col></el-row></el-form-item><el-form-item><div class="login-btn"><el-button type="primary" @click="gologin()" style="margin-left: auto;width: 35%">返回登录</el-button><el-button type="primary" @click="submitForm()" style="margin-left: 27%;width: 35%" >确定</el-button></div></el-form-item></el-form></div>
</template><script>
import SIdentify from "../../components/SIdentify";
import axios from "axios";export default {name: "register",components: {SIdentify},data() {return {formRegist: {account: "",username: "",password: "",againpassword: "",code: ""},flag: '',pass: '1',identifyCodes: '1234567890abcdefjhijklinopqrsduvwxyz',//随机串内容identifyCode: '',// 校验rules: {username:[{required: true, message: "请输入用户名", trigger: "blur"}],account:[{required: true, message: "请输入账号", trigger: "blur"}],password: [{required: true, message: "请输入密码", trigger: "blur"}],againpassword: [{required: true, message: "请再次输入密码", trigger: "blur"}],code: [{required: true, message: "请输入验证码", trigger: "blur"}]}}},mounted() {// 初始化验证码this.identifyCode = ''this.makeCode(this.identifyCodes, 4)},methods: {refreshCode() {this.identifyCode = ''this.makeCode(this.identifyCodes, 4)},makeCode(o, l) {for (let i = 0; i < l; i++) {this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]}},randomNum(min, max) {return Math.floor(Math.random() * (max - min) + min)},gologin() {this.$router.push("/login")},RightDataInput(){if(this.formRegist.account.length<4){this.pass='0'alert("账号长度不得小于4")}if(this.formRegist.password.length<8){this.pass='0'alert("账号长度不得小于8")}if(this.formRegist.account===null || this.formRegist.password===null){this.pass='0'this.$message.error('请填写账号或密码')}if(this.formRegist.password.toLowerCase() !== this.formRegist.againpassword.toLowerCase()){this.pass='0'this.$message.error('密码与上次输入不匹配')alert('密码与上次输入不匹配')}},Register(){this.axios({url: "/boot/register",method: 'post',headers: { "type": "hello" },data: {account:  this.formRegist.account,username: this.formRegist.username,password: this.formRegist.password.toLowerCase(),}}).then(res =>{this.flag = res.data.flag;if(this.flag =='1'){alert("注册成功")this.$router.push("/login")}else {alert("注册失败!")}})},submitForm() {this.RightDataInput()if (this.formRegist.code.toLowerCase() !== this.identifyCode.toLowerCase()) {this.$message.error('请填写正确验证码')this.refreshCode()}else {if(this.pass=='1'){console.log("账号提交注册")this.Register()}}},}}
</script><style scoped>
.login-bok{width: 30%;margin: 150px auto;border: 1px solid #DCDFE6;padding: 20px;border-radius: 10px;box-shadow: 0 0 30px #DCDFE6;
}
</style>

我这里连函数都没有封装,是很能够看懂的哈。

主页面

这个就是我们的重点了。

首先这里调用了三个接口。


这三个接口一目了然是吧。

流程

不过在开始之前,我们先简单来说说,这玩意的调用流程,这个非常重要。

大概就是这样的,因为我们是有token的,所以我们只需要拿到token就可以去确定用户身份,然后设置session。
token 是为了做七天保持登录,如果用session,浏览器关了就没了,存储在localstage得加个密,所以直接使用token。

websocket


这几个点就不用多说了。
主要是,第一用户来了消息要显示出来,就是那个角标要出来,消息提示要出来。
这个是这样做的。
有个集合,

消息过来了,就

读完了,我就把这编号删掉,这样就搞定了。


那么这个就是websocket

loadmessage

然后是信息加载。这个没啥好说的。

访问之后,把那个数据渲染一下。

消息发送

这个消息发送也简单,主要是修改数据就可以。

完整代码

ok,这个比较主要的点说完了,我们来看看这个完整代码。

<template>
<div><div style="width: 70%;height: 500px;margin: 0 auto"><el-container><el-header style="color: white"><p>欢迎来到随聊</p><el-button type="success" round style="position: fixed;left: 70%;top: 12%" @click="loginout">退出登录</el-button></el-header><div  style="height: 40px;width: 100%;background-color: #1794c9"><p style="margin-right: 85%;color: white">在线聊友</p></div><el-container><el-aside width="200px"><div  style="width: 180px;height: 500px;margin: 20px auto"><el-row><el-card style="height: 70px" shadow="hover" v-for="(user,index) in Users" :key="index"><el-button  v-if="selectId==user.id" type="primary"style="width: 100%" @click="select(user.id,user.username)"><p v-if="user.id!=currentId"><i>{{user.username}}</i></p><p v-else>ME</p></el-button><el-button v-else type="primary" plainstyle="width: 100%" @click="select(user.id,user.username)"><p v-if="user.id!=currentId"><el-badge v-if="badgeMes(user.id)" value="new" class="item"><i>{{user.username}}</i></el-badge><el-badge v-else><i>{{user.username}}</i></el-badge></p><p v-else>ME</p></el-button></el-card></el-row></div></el-aside><el-main><el-main class="show" style="width: 90%;margin: 0 auto;height: 350px;background-color: #f0faff;border: 5px #2981ce;border-radius: 10px;"><div >
<!--            这部分是咱们的对话内容--><div style="width: 90%;height: 80px;margin: 0 auto"v-for="(Message,index) in MessagesList" :key="index"><div v-model="currentId" v-if="currentId!==Message.fromID && selectId==Message.fromID"><br><div  style="display:inline-block;width: 10%;border: 1px solid #14e0bf;;font-size: 3px;border-radius: 100px"><p style="text-align: center;">{{Message.fromName}}:</p></div><div style="display:inline-block;width: 60%;border-radius: 10px;border: 1px solid #0c93ef;"><p style="width: 100%;text-align: left">{{Message.message.message}}</p></div></div><div v-model="currentId" v-if="currentId===Message.fromID  && selectId==Message.message.toID"><div style="display:inline-block;width: 60%;border-radius: 10px;border: 1px solid #0c93ef;"><p style="width: 100%;text-align: right">{{Message.message.message}}</p></div><div  style="display:inline-block;width: 10%;border: 1px solid #14e0bf;;font-size: 3px;border-radius: 100px"><p style="text-align: center;">:{{currentName}}</p></div></div></div></div></el-main><br><div style="width: 90%;margin: 0 auto;background-color: white;border-radius: 5px;"><el-input v-model="sendMsg" type="textarea" :rows="3"placeholder="说点什么吧~"></el-input><br><br><el-button style="width: 15%;margin-left: 85%" @click="submit" type="primary">发送</el-button></div></el-main></el-container></el-container></div><router-view/>
</div>
</template><script>export default {name: "main",data() {return {read: new Set(),lastselectId: -2,selectId: -1,currentId: null,currentName: null,sendMsg: null,sending: null,MessagesList: [],Users:[{"id":1,"username":"小米"},{"id":2,"username":"小明"},{"id":3,"username":"小铭"},{"id":4,"username":"小敏"},]}},beforeRouteEnter: (to, from, next) => {console.log("准备进入主页");let islogin = localStorage.getExpire("token")if(!islogin){next({path:'/login'});}next();},methods:{freshMyMessage(sendMsg){//  这个主要是自己发送的消息不会再被服务器转发给自己,需要本地刷新一下let MyMessage={"isSystem": false,"fromID": this.currentId,"fromName": this.currentName,"message":sendMsg}this.MessagesList.push(MyMessage)},badgeMes(ID){return this.read.has(Number(ID));},loadMessage(selectID,userName){this.axios({url: "/boot/loadmessage",method: 'post',data: {currentId:  this.currentId,currentName: this.currentName,selectID: selectID,userName: userName}}).then(res =>{let data = res.datathis.MessagesList = data})},select(selectID,userName){this.selectId = selectIDthis.read.delete(Number(selectID))//此时选的是别人,需要调用后端数据库获取以前的聊天记录,并且覆盖掉当前的记录//为了防止用户恶意点击还需要记录一下是不是点击的同一个人if(this.selectId==this.currentId) {this.MessagesList=[]}if(this.lastselectId!=this.selectId && this.selectId!=this.currentId){//执行消息覆盖//这一部分其实是要访问数据库,来寻找聊天消息的,这里我不想把数据存在本地//一方面是因为用户端负载,另一方面,换了浏览器就没了,还是要存在数据库里面this.loadMessage(selectID,userName)this.lastselectId = this.selectId}else {if(this.lastselectId==this.selectId){alert("正在和当前用户聊天")}}},submit(){if(this.selectId==-1){alert("请选择聊天对象")return;}if(this.sendMsg===null || this.sendMsg.length<1){alert("请输入您的消息")return}if(this.currentId==this.selectId){alert("不能和自己聊天哟~")return;//  选的是自己没用}this.sendMessage()this.sendMsg=nullconsole.log("已发送信息")},loginout(){localStorage.removeItem("token");this.$router.push('/login')},getUserInfo(){this.axios({url: "/boot/main",method: 'get',headers: { "token": localStorage.getExpire("token") },}).then(res =>{let data = res.dataif(data.success == '0'){alert("数据获取异常,请重新登录!")this.loginout()return;}if(data.success=='2'){alert("登录过期请重新登录")this.loginout()}this.currentId = data.currentIdthis.currentName  = data.currentName})},//  这个是咱们websocket的方法//建立连接,初始化weosocketsendMessage() {let toName = nullfor(var i=0;i<this.Users.length;i++){if(this.Users[i].id==this.selectId){toName = this.Users[i].usernamebreak}}this.sending={"toID":this.selectId,"toName":toName,"message":this.sendMsg}this.freshMyMessage(this.sending)this.socket.send(JSON.stringify(this.sending));},//初始化建立前后台链接init() {if (typeof WebSocket === "undefined") {alert("您的浏览器不支持socket");} else {// 实例化socketthis.socket = new WebSocket("ws://localhost:8000/chat");// 监听socket连接this.socket.onopen = this.open;// 监听socket错误信息this.socket.onerror = this.error;// 监听socket消息this.socket.onmessage = this.getMessage;this.socket.onclose = this.close;}},//链接成功时的回调函数open() {console.log("socket连接成功");},//链接错误时的回调error(err) {console.log("连接错误" + err);},//后台消息推送过来,接收的函数,参数为后台推过来的数据。getMessage(msg) {let dataJson = JSON.parse(msg.data)//我们的消息是分为两大类的,一个是系统消息,还有一个是用户消息if(dataJson.system){this.Users = dataJson.message}else {console.log(dataJson)this.MessagesList.push(dataJson)this.read.add(dataJson.fromID)}},//链接关闭的回调close(event) {//socket是链接的实例,close就是关闭链接this.socket.close()console.log("断开链接成功");},},mounted() {this.getUserInfo()this.init()}
}
</script><style scoped>
.el-header, .el-footer {background-color: #1794c9;color: #333;text-align: center;line-height: 60px;
}.show:hover{box-shadow: 0px 15px 30px rgb(30, 136, 225);margin-top: 20px;}</style>

后端

那么现在我们把目光搞到后端部分。

环境

我们先来看到环境部分,这个部分呢,用到真实的依赖其实不多。然后这一次是用mybatis来做数据存储的。
然后使用hashmap来存储在线用户的标识,这样的作用和redis的作用其实一样的,都是直接存在内存里面的,所以说速度是可以的,而且没有连接的延时,不过坏处是,redis可以部署到专门的服务器里面,这个不行,不过理论上,我们可以考虑搞一个服务器专门hashmap存储内容,然后发送连接拿到数据也可以,但是这样的话不如直接使用redis不过,这个也不失为一种想法,因为用这玩意我可以自定义复杂对象。

好了,废话不多说了,我们来看看这个依赖。

<?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.5.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.huterox</groupId><artifactId>second</artifactId><version>0.0.1-SNAPSHOT</version><name>second</name><description>second</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!--        热更新的配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.6.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

自己看着复制哈(其实这个是我第二次Java作业,所以不方便直接给项目文件)

完整的项目结构长这个样子

配置


server :port : 8000spring:devtools:restart:enabled: truedatasource:druid:username: Huteroxpassword: 865989840url: jdbc:mysql://localhost:3306/second?useSSL=false&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Driveraop-patterns: com.atguigu.admin.*  #springbean监控filters: stat,wall,slf4j  #所有开启的功能stat-view-servlet: #监控页配置enabled: truelogin-username: adminlogin-password: adminresetEnable: falseweb-stat-filter: #web监控enabled: trueurlPattern: /*exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'filter:stat: #sql监控slow-sql-millis: 1000logSlowSql: trueenabled: truewall: #防火墙enabled: trueconfig:drop-table-allow: false
mybatis:mapperLocations: classpath:mapper/*.xmlconfig-location: classpath:mybatis-config.xmltype-aliases-package: com.huterox.second.dap.pojo

其他的几个配置文件是没啥用的

数据库设计

这个呢,我设计了四个数据库,不过用到的只有三个,因为原来想做的是有在线群聊的玩意,后来发现这个前端不好写,所以砍掉了,不过如果你感兴趣拓展其实也很简单,因为所有用户的消息其实我是已经发到前端了,只是前端怎么渲染的问题,怎么设计的问题,然后后端继续加几个接口。这个很简单的,没啥说的。



这个表的关联应该很好看懂吧,引入了几个外键,不过我是没有在数据库里面直接使用外键的,都是在代码层面去做的。

然后是我们对应的pojo类



Dao层及其mapper

然后是写各种增删查改,由于是myabtis所以要那啥,写sql,还是mp香呀。然后为了方便管理,所以我的sql语句都是在xml文件里面的。


由于都是对得到的,所以我们这边就直接把xml文件展示出来

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.huterox.second.dao.mapper.FriendMapper"><!--sql--><select id="getAllFriends" resultType="com.huterox.second.dao.pojo.Friend">select * from friend</select>
</mapper>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.huterox.second.dao.mapper.MessageMapper"><insert id="addMessage">INSERT INTO message (message,talkid) VALUES (#{message},#{talkid})</insert><!--sql--><select id="getAllMessages" resultType="com.huterox.second.dao.pojo.Message">select * from message</select><select id="getMessagesByTalkID" resultType="com.huterox.second.dao.pojo.Message">select * from message where talkid=#{talkid}</select>
</mapper>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.huterox.second.dao.mapper.TalkMapper"><insert id="addTalk">INSERT INTO talk (mytalk,shetalk) VALUES (#{mytalk},#{shetalk})</insert><!--sql--><select id="getAllTalks" resultType="com.huterox.second.dao.pojo.Talk">select * from talk</select><select id="findTalk" resultType="com.huterox.second.dao.pojo.Talk">select * from talk where mytalk=#{mytalk} and shetalk=#{shetalk}</select>
</mapper>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.huterox.second.dao.mapper.UserMapper"><insert id="AddUser">INSERT INTO user (account, username, password) VALUES (#{account},#{username},#{password})</insert><!--sql--><select id="getAllUsers" resultType="com.huterox.second.dao.pojo.User">select * from user</select><select id="selectUserByAccount" resultType="com.huterox.second.dao.pojo.User">select * from user where account=#{account}</select><select id="selectUserByAccountAndPassword" resultType="com.huterox.second.dao.pojo.User">select * from user where account=#{account} and password=#{password}</select><select id="selectUserById" resultType="com.huterox.second.dao.pojo.User">select * from user where id=#{id}</select></mapper>

到这里dao其实就差不多了

Dao相关的服务

服务就三个

package com.huterox.second.server;import com.huterox.second.dao.mapper.MessageMapper;
import com.huterox.second.dao.pojo.Message;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class MessageService {@AutowiredMessageMapper messageMapper;public Long SaveMessage(String message,Long talkid){return messageMapper.addMessage(message, talkid);}public List<Message> getMessagesByTalkID(Long talkid){return messageMapper.getMessagesByTalkID(talkid);};
}
package com.huterox.second.server;import com.huterox.second.dao.mapper.TalkMapper;
import com.huterox.second.dao.pojo.Talk;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class TalkService {@AutowiredTalkMapper talkMapper;public Long getTalkId(Long mytalk,Long shetalk){Talk talk = talkMapper.findTalk(mytalk, shetalk);if(talk!=null){return talk.getTid();}else {//            由于我们的主键不是账号,所以没法指定,只能重新查询talkMapper.addTalk(mytalk,shetalk);talk = talkMapper.findTalk(mytalk, shetalk);return talk.getTid();}}
}
package com.huterox.second.server;import com.huterox.second.dao.mapper.UserMapper;
import com.huterox.second.dao.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserService {@AutowiredUserMapper userMapper;public User selectUserById(Integer id){return userMapper.selectUserById(id);}public User selectUserByAccount(String account){return userMapper.selectUserByAccount(account);}public User selectUserByAccountAndPassword(String account,String password){return userMapper.selectUserByAccountAndPassword(account,password);}@Transactional(rollbackFor = Exception.class)public int addUser(String account,String username,String password) throws Exception {//        发生异常回滚int flag = 1;try {userMapper.AddUser(account, username, password);}catch (Exception e){flag = -1;throw new Exception("用户添加异常");}return flag;}
}

登录注册实现

交互信息

在此之前,我们还需要明确一下这个后端会返回给前端的数据。

这个很重要





登录注册

ok,前面铺垫了那么多终于到了登录注册这种服务层面了。

首先这个业务层面没什么好说的

package com.huterox.second.controller;import com.huterox.second.dao.message.LoginMessage;
import com.huterox.second.dao.pojo.User;
import com.huterox.second.server.UserService;
import com.huterox.second.utils.TokenProccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpSession;
import java.util.Map;@Controller
@ResponseBody
public class Login {@AutowiredUserService userService;@AutowiredLoginMessage loginMessage;@AutowiredTokenProccessor tokenProccessor;@PostMapping(value = "/login" )public LoginMessage login(@RequestBody Map<String,Object> accountMap, HttpSession session){String account;String username;String password;User user = null;try {account = (String) accountMap.get("account");password = (String) accountMap.get("password");user = userService.selectUserByAccountAndPassword(account,password);}catch (Exception e){e.printStackTrace();loginMessage.setSuccess(-1);}if(user!= null){//            这里我们把用户的id给前端,后面我们验证这个id就好了String token = tokenProccessor.createToken(String.valueOf(user.getId()));loginMessage.setSuccess(1);loginMessage.setToken(token);}else {loginMessage.setSuccess(-1);}return loginMessage;}
}
package com.huterox.second.controller;import com.huterox.second.dao.message.RegisterMessage;
import com.huterox.second.server.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.Map;@Controller
@ResponseBody
public class Register {@AutowiredRegisterMessage registerMessage;@AutowiredUserService userService;@PostMapping("/register")public RegisterMessage Register(@RequestBody Map<String, Object> userMap) throws Exception {String account = (String) userMap.get("account");String username = (String)userMap.get("username");String password = (String) userMap.get("password");if(account!=null && password!=null){userService.addUser(account,username,password);registerMessage.setFlag(1);}else {registerMessage.setFlag(-1);}return registerMessage;}
}

这样要说的是这个拦截器,和token加密

token生产解析

这个是使用jwt来做的。首先这里封装了一个工具类。

package com.huterox.second.utils;import com.huterox.second.dao.pojo.User;
import com.huterox.second.server.UserService;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;@Component
public class TokenProccessor {@AutowiredUserService userService;private static final long EXPIRE_TIME=60*60*1000*24*7; //过期时间7天private static final String KEY = "huterox"; //加密秘钥/*** 生成token* 由于只有当账号密码正确之后才会生成token所以这边只需要用户名进行识别* @param account  用户账号* @return*/public  String createToken(String account){Map<String,Object> header = new HashMap<String,Object>();header.put("typ","JWT");header.put("alg","HS256");JwtBuilder builder = Jwts.builder().setHeader(header).setExpiration(new Date(System.currentTimeMillis()+EXPIRE_TIME)).setSubject(account)//设置信息,也就是用户名.setIssuedAt(new Date()).signWith(SignatureAlgorithm.HS256,KEY);//加密方式return builder.compact();}/*** 验证token是否有效* @param token  请求头中携带的token* @return  token验证结果  2-token过期;1-token认证通过;0-token认证失败*/public int verify(String token){Claims claims = null;try {//token过期后,会抛出ExpiredJwtException 异常,通过这个来判定token过期,claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(token).getBody();}catch (ExpiredJwtException e){return 2;}//从token中获取用户名,当用户查询通过后即可String id = claims.getSubject();
//        User user = userService.selectUserById(Integer.parseInt(id));if(id != null){return 1;}else{return 0;}}public String GetIdByToken(String token){Claims claims = null;try {//token过期后,会抛出ExpiredJwtException 异常,通过这个来判定token过期,claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(token).getBody();}catch (ExpiredJwtException e){return null;}//从token中获取用户名,当用户查询通过后即可String id = claims.getSubject();return id;}}

拦截器

之后是拦截器

package com.huterox.second.config;import com.huterox.second.server.UserService;
import com.huterox.second.utils.TokenProccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.ArrayList;
import java.util.List;@Configuration
public class TokenConfig implements WebMvcConfigurer {@AutowiredUserService userService;@AutowiredTokenProccessor tokenProccessor;//    @Override
//    public void addCorsMappings(CorsRegistry registry) {//        registry.addMapping("/**")
//                .allowCredentials(true)
//                .allowedHeaders("*")
//                .allowedMethods("*")
//                .allowedOrigins("*");
//
//    }// 跨域,我们前端做了跨域所以的话后端不用,也是为了安全,不能什么阿猫阿狗都过来访问@Overridepublic void addInterceptors(InterceptorRegistry registry){List<String> excludePath = new ArrayList<>();//排除拦截,除了注册登录(此时还没token),其他都拦截excludePath.add("/register");  //登录excludePath.add("/login");     //注册excludePath.add("/chat");       //excludePath.add("/loadmessage");excludePath.add("/static/**");  //静态资源excludePath.add("/assets/**");  //静态资源registry.addInterceptor(new TokenInterceptor(tokenProccessor)).addPathPatterns("/**").excludePathPatterns(excludePath);WebMvcConfigurer.super.addInterceptors(registry);}}
package com.huterox.second.config;import com.alibaba.fastjson.JSON;
import com.huterox.second.dao.message.TokenInMessage;
import com.huterox.second.server.UserService;
import com.huterox.second.utils.TokenProccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class TokenInterceptor implements HandlerInterceptor {TokenProccessor tokenProccessor;public TokenInterceptor(TokenProccessor tokenProccessor) {this.tokenProccessor = tokenProccessor;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{if(request.getMethod().equals("OPTIONS")){response.setStatus(HttpServletResponse.SC_OK);return true;}response.setCharacterEncoding("utf-8");String token = request.getHeader("token");int result = 0;if(token != null){result = tokenProccessor.verify(token);if(result == 1){//                System.out.println("通过拦截器");return true;}}response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");try{TokenInMessage tokenInMessage = new TokenInMessage();tokenInMessage.setSuccess(result);//0表示验证失败,2表示过期response.getWriter().append(JSON.toJSONString(tokenInMessage));
//            System.out.println("认证失败,未通过拦截器");}catch (Exception e){e.printStackTrace();response.sendError(500);return false;}return false;}
}

到这里的话一个完整的用户登录流程是开发好了。
那么接下来就是基于这个破玩意来干活了

聊天室

现在进入我们的另一个大头。
首先是配置。

package com.huterox.second.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {@Bean//注入ServerEndpointExporter bean对象,自动注册使用注解@ServerEndpoint的beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}

进入聊天室

进入这个聊天室的话,需要给上session,这样才能辨别用户。

package com.huterox.second.controller;import com.huterox.second.dao.message.MainMessage;
import com.huterox.second.dao.pojo.User;
import com.huterox.second.server.UserService;
import com.huterox.second.utils.TokenProccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Objects;@Controller
@ResponseBody
public class Main {@AutowiredMainMessage mainMessage;@AutowiredUserService userService;@AutowiredTokenProccessor tokenProccessor;@RequestMapping("/main")public MainMessage main(HttpServletRequest request, HttpSession session){//        能够进来这个页面说明是已经登录了的String token = request.getHeader("token");Long id = (Long) session.getAttribute("id");String name = (String) session.getAttribute("name");System.out.println("id:"+id+"-name:"+name+"进入Mian");if(id!=null && name!=null){//            如果有人恶意修改session,我也没辙,反正到时候恶意修改后数据可能是拿不到的mainMessage.setSuccess(1);mainMessage.setCurrentId(id);mainMessage.setCurrentName(name);}else {User user = userService.selectUserById(Integer.parseInt(Objects.requireNonNull(tokenProccessor.GetIdByToken(token))));if (user!=null){//                我们在这里的目的是为了设置session,这样好进行聊天标记session.setAttribute("id",user.getId());session.setAttribute("name",user.getUsername());mainMessage.setSuccess(1);mainMessage.setCurrentId(user.getId());mainMessage.setCurrentName(user.getUsername());}else {mainMessage.setSuccess(0);}}return mainMessage;}
}

聊天部分

首先聊天有两个部分,一个是广播所有用户,一个是转发,因为有用户登录的时候需要告诉别的用户。
这个部分,有代码注释,我在这里就不说了。
这里主要使用到两个东西。
一个是让websocket获取session的玩意

package com.huterox.second.websocket;import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {//获取HttpSession对象HttpSession httpSession = (HttpSession) request.getHttpSession();sec.getUserProperties().put(HttpSession.class.getName(),httpSession);}
}

还有一个是转化信息的工具类

package com.huterox.second.utils;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huterox.second.dao.message.ResultMessage;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;@Component
public class MessageUtils {public static String getMessage(boolean isSystemMessage,Long fromID,String fromName,Object message){try {ResultMessage result = new ResultMessage();result.setSystem(isSystemMessage);result.setFromName(fromName);result.setMessage(message);if (fromID!=null){result.setFromID(fromID);}//把字符串转成json格式的字符串ObjectMapper mapper = new ObjectMapper();return mapper.writeValueAsString(result);}catch (JsonProcessingException e){e.printStackTrace();}return null;}
}

当然还有存储的服务,不过这个在service里面做了。

完整代码

ok,现在看看完整代码

package com.huterox.second.websocket;import com.fasterxml.jackson.databind.ObjectMapper;
import com.huterox.second.dao.message.MessageSend;
import com.huterox.second.server.MessageService;
import com.huterox.second.server.TalkService;
import com.huterox.second.utils.MessageUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;@RestController
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfigurator.class)
public class ChatEndpoint {private static TalkService talkService;private static MessageService messageService;@Autowiredpublic void setTalkService(TalkService talkService){ChatEndpoint.talkService = talkService;}@Autowiredpublic void setMessageService(MessageService messageService){ChatEndpoint.messageService = messageService;}//用来存储每个用户客户端对象的ChatEndpoint对象private static Map<Long,ChatEndpoint> onlineUsers = new ConcurrentHashMap<>();private static Map<Long,String> onlineUserNames = new ConcurrentHashMap<>();
//    用来存储talkID 的这样只需要查询一次数据库就可以拿到talkID了,加快速度private static Map<String,Long> talkID = new ConcurrentHashMap<>();//声明session对象,通过对象可以发送消息给指定的用户private Session session;//声明HttpSession对象,我们之前在HttpSession对象中存储了用户idprivate HttpSession httpSession;//连接建立@OnOpenpublic void onOpen(Session session, EndpointConfig config){this.session = session;HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());this.httpSession = httpSession;//存储登陆的对象Long userID = (Long) httpSession.getAttribute("id");String name = (String) httpSession.getAttribute("name");System.out.println("id:"+userID+"-name:"+name+"进入聊天室");onlineUsers.put(userID,this);onlineUserNames.put(userID,name);//将当前在线用户的用户名推送给所有的客户端//1 获取消息String message = MessageUtils.getMessage(true, null, null,getUsers());//2 调用方法进行系统消息的推送broadcastAllUsers(message);}private void broadcastAllUsers(String message){try {//将消息推送给所有的客户端Set<Long> IDS = onlineUsers.keySet();for (Long ID : IDS) {ChatEndpoint chatEndpoint = onlineUsers.get(ID);chatEndpoint.session.getBasicRemote().sendText(message);}}catch (Exception e){e.printStackTrace();}}//返回在线用户IDprivate Set<Long> getId(){return onlineUsers.keySet();}//返回在线用户名
//    {"id":4,"username":"小敏"},private Set<Map<String,String>> getUsers(){Set<Map<String,String>> set = new HashSet<>();for (Map.Entry<Long, String> entry : onlineUserNames.entrySet()) {Map<String,String> temp = new HashMap<>();temp.put("id",String.valueOf(entry.getKey()));temp.put("username",entry.getValue());set.add(temp);}return set;}private Long getTalkID(Long mytalk,Long shetalk){String Key = mytalk+"to"+shetalk;if (talkID.get(Key)!=null){return talkID.get(Key);}else {Long talkId = talkService.getTalkId(mytalk, shetalk);talkID.put(Key,talkId);return talkId;}}private Long SaveMessage(Long mytalk,Long shetalk,String message){Long talkID = this.getTalkID(mytalk, shetalk);return messageService.SaveMessage(message, talkID);}//收到消息,并且我们需要把消息存储到数据库内@OnMessagepublic void onMessage(String message, Session session){//将数据转换成对象try {ObjectMapper mapper =new ObjectMapper();System.out.println(message);MessageSend mess = mapper.readValue(message, MessageSend.class);Long toID = mess.getToID();String toName = mess.getToName();String data = mess.getMessage();Long userID = (Long) httpSession.getAttribute("id");String userName = (String) httpSession.getAttribute("name");
//            在这里存储消息this.SaveMessage(userID,toID,data);System.out.println(mess);String resultMessage = MessageUtils.getMessage(false, userID,userName,mess);//发送数据if(toID!=null) {//                发送的数据长这个样子
//                {"isSystem": false,
//                        "fromID": 2,
//                        "message":{"toID":1,"toName":"Futerox","message":"Hello"}
//                }onlineUsers.get(toID).session.getBasicRemote().sendText(resultMessage);}} catch (Exception e) {e.printStackTrace();}}//关闭@OnClosepublic void onClose(Session session) {Long userID = (Long) httpSession.getAttribute("id");//从容器中删除指定的用户onlineUsers.remove(userID);onlineUserNames.remove(userID);String message = MessageUtils.getMessage(true,null,null,getUsers());broadcastAllUsers(message);}}

聊天信息加载

终于到了最后一步了,聊天信息的加载。

这一步主要是靠这玩意

代码如下:

package com.huterox.second.utils;
import com.huterox.second.dao.message.MessageSend;
import com.huterox.second.dao.message.ResultMessage;
import com.huterox.second.dao.pojo.Message;
import com.huterox.second.server.MessageService;
import com.huterox.second.server.TalkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;@Component
public class LoadMessageUtils {//这个玩意主要是拿过来返回消息列表的,方便加载
//    加载的消息有两方面,一个是你自己和别人说的,另一个是别人跟你说的,所以需要两个查询private static TalkService talkService;private static MessageService messageService;@Autowiredpublic void setTalkService(TalkService talkService){LoadMessageUtils.talkService = talkService;}@Autowiredpublic void setMessageService(MessageService messageService){LoadMessageUtils.messageService = messageService;}public static List<ResultMessage> LoadMessages(Long mytalk,Long shetalk,String myName,String sheName){//        我们的写操作要比读操作更多,所以不用CopyOnWriteArrayList<>();List<ResultMessage> resultMessages= Collections.synchronizedList(new ArrayList<ResultMessage>());Long mytalkID = talkService.getTalkId(mytalk,shetalk);Long shetalkID = talkService.getTalkId(shetalk,mytalk);if(mytalkID!=null){addMessages(mytalkID,resultMessages,myName,sheName,mytalk,shetalk);}if (shetalkID!=null){addMessages(shetalkID,resultMessages,sheName,myName,shetalk,mytalk);}return resultMessages;}private static void addMessages(Long talkID,List<ResultMessage> LoadMessages,String fromName,String toName,Long mytalk,Long shetalk){List<Message> messagesByTalkID = messageService.getMessagesByTalkID(talkID);for (Message message : messagesByTalkID) {ResultMessage resultMessage_ = new ResultMessage();MessageSend messageSend_ = new MessageSend();resultMessage_.setFromID(mytalk);resultMessage_.setFromName(fromName);messageSend_.setMessage(message.getMessage());messageSend_.setToID(shetalk);messageSend_.setToName(toName);resultMessage_.setMessage(messageSend_);LoadMessages.add(resultMessage_);}}
}

总结

到这里的话,认认真真看这篇博文的话,应该是可以直接复刻这个项目的,毕竟这个底裤都拿出来了,这dome。
然后优化的是有很多优化的。对了这里就不得不说这个一个小bug了,这个是前端的问题,就是这个页面在chrome浏览器是没有啥问题的,但是在Firefox,这个用户名渲染会出现问题。其他的还好说。

嘿从零开始基于SpringBoot 打造在线聊天室(4.4W字最长博文)相关推荐

  1. SpringBoot搭建在线聊天室

    Echat-SpringBoot 一款轻量级的基于SpringBoot + WebSocket的在线聊天室项目,在MccreeFei的聊天室基础上,将其升级为SpringBoot版本,去掉了JSP文件 ...

  2. 从头搭建一个基于 Python 的在线聊天室

    本场 Chat,是基于 Python + Redis + Flask 来搭建一个简单易用的在线聊天室.完全从零开始,一步一步完成整个项目. 主要分享内容: Flask 项目结构 Python Redi ...

  3. Java+Springboot+Websocket在线聊天室

    1.什么是websocket? websocket是由HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯.它是一种在单个TCP连接上进行全双工通信的协议.W ...

  4. 【php】基于Xajax的在线聊天室、直播间

    关于php的Xajax到底是什么,这里不再介绍,详情可以看我<[php]Xajax Helloworld>(点击打开链接)与<[php]注册系统和使用Xajax即时验证用户名是否被占 ...

  5. Java网络编程,使用Java实现UDP和TCP网络通信协议,以及基于UDP的在线聊天室。

    文章目录 前言 一.网络编程概念 1.网络 2. 网络编程的目的 3.想要达到这个效果需要什么 4.网络分层 二.网络编程Java类 1.IP地址:InetAddress 2.端口 3.TCP连接 3 ...

  6. SpringBoot 使用WebSocket打造在线聊天室(基于注解)

    点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 精彩内容 java实战练习项目教程 2018微服务资源springboot.s ...

  7. .NET Core 实现基于Websocket的在线聊天室

    什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...

  8. websocket一直无法链接_.NET Core 实现基于Websocket的在线聊天室

    什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...

  9. 基于netty的在线聊天室,支持群聊和私聊——【一】基本功能介绍和nginx配置

    netty虽然可以实现聊天室的功能.但完整的做下来,还是要自己去封装很多东西,尤其是客户端和服务器通信的数据格式,服务端消息派发器的设计.这一点就比spring 的websocket over sto ...

最新文章

  1. 你应该知道的 Nginx 配置清单
  2. UIApplicaton详情
  3. 几分钟学会归并排序和快速排序
  4. 一分二功率分配器_一文学会微波功率分配器
  5. C# ASP.NET 权限设计 完全支持多数据库多语言包的通用权限管理系统组件源码
  6. 暴风集团仅剩10余人;搜狗告百度输入法侵权案再驳回;Linux 5.6发布 | 极客头条...
  7. pycharm新建python文件快捷键_Pycharm快捷键
  8. torch/utils/cpp_extension.py raise RuntimeError(message) from e
  9. 3DMAx Panda Directx Exporter 导出 X插件
  10. 大数据资源争夺战此起彼伏,对用户而言是福是祸
  11. 赠书!《R语言数据分析与可视化从入门到精通》
  12. 蝴蝶效应 青蛙现象 鳄鱼法则 马太效应 木桶理论 二八定律(巴莱多定律) 破窗理论 羊群效应
  13. Word无法打开该文件,因为文件格式与文件扩展名不匹配 | 无法从该位置打开扩展名为.asd的文件
  14. 2021强网杯 Web赌徒 WP
  15. 哪款蓝牙耳机的音质好?四款音质最好的蓝牙耳机测评
  16. 全网最全关闭小米手机MIUI系统广告教程
  17. 几道js数组循环练习题
  18. 今天才发现,手机外放声音小,这样设置一下,轻松增大手机音量
  19. 水印相机定位不准确怎么办_云联相机app下载-云联相机app安卓版下载v1.0.0
  20. 期货中的“心静”和“身静”

热门文章

  1. layui table 每列加标签_【前端】layui表格中根据条件给对应的列加背景色
  2. 日本知识产权局新设物联网相关技术专利分类
  3. 【洛谷 P5550】 Chino的数列【矩阵乘法】
  4. 在网站中插入 英文地图非谷歌
  5. 微服务 - Hystrix 熔断器
  6. webgl - 实现景深效果(一)
  7. OJ每日一练——雇佣兵
  8. linux yum 安装的路径在哪,yum 下载软件的存放位置
  9. 白手起家成就亿万富翁梦想的企业家和普通人的10点不同之处
  10. 备注: ubt 16.04 安装 gtx 1060 --- 成功运行 tensorflow - gpu