嘿从零开始基于SpringBoot 打造在线聊天室(4.4W字最长博文)
文章目录
- 前言
- 效果
- 主页面
- 消息提示
- 聊天页面
- 登录注册
- 前端
- 项目构建
- 依赖
- 项目结构
- 登录注册
- 验证码部分
- 登录页面
- 注册页面
- 主页面
- 流程
- 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字最长博文)相关推荐
- SpringBoot搭建在线聊天室
Echat-SpringBoot 一款轻量级的基于SpringBoot + WebSocket的在线聊天室项目,在MccreeFei的聊天室基础上,将其升级为SpringBoot版本,去掉了JSP文件 ...
- 从头搭建一个基于 Python 的在线聊天室
本场 Chat,是基于 Python + Redis + Flask 来搭建一个简单易用的在线聊天室.完全从零开始,一步一步完成整个项目. 主要分享内容: Flask 项目结构 Python Redi ...
- Java+Springboot+Websocket在线聊天室
1.什么是websocket? websocket是由HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯.它是一种在单个TCP连接上进行全双工通信的协议.W ...
- 【php】基于Xajax的在线聊天室、直播间
关于php的Xajax到底是什么,这里不再介绍,详情可以看我<[php]Xajax Helloworld>(点击打开链接)与<[php]注册系统和使用Xajax即时验证用户名是否被占 ...
- Java网络编程,使用Java实现UDP和TCP网络通信协议,以及基于UDP的在线聊天室。
文章目录 前言 一.网络编程概念 1.网络 2. 网络编程的目的 3.想要达到这个效果需要什么 4.网络分层 二.网络编程Java类 1.IP地址:InetAddress 2.端口 3.TCP连接 3 ...
- SpringBoot 使用WebSocket打造在线聊天室(基于注解)
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 精彩内容 java实战练习项目教程 2018微服务资源springboot.s ...
- .NET Core 实现基于Websocket的在线聊天室
什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...
- websocket一直无法链接_.NET Core 实现基于Websocket的在线聊天室
什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...
- 基于netty的在线聊天室,支持群聊和私聊——【一】基本功能介绍和nginx配置
netty虽然可以实现聊天室的功能.但完整的做下来,还是要自己去封装很多东西,尤其是客户端和服务器通信的数据格式,服务端消息派发器的设计.这一点就比spring 的websocket over sto ...
最新文章
- 你应该知道的 Nginx 配置清单
- UIApplicaton详情
- 几分钟学会归并排序和快速排序
- 一分二功率分配器_一文学会微波功率分配器
- C# ASP.NET 权限设计 完全支持多数据库多语言包的通用权限管理系统组件源码
- 暴风集团仅剩10余人;搜狗告百度输入法侵权案再驳回;Linux 5.6发布 | 极客头条...
- pycharm新建python文件快捷键_Pycharm快捷键
- torch/utils/cpp_extension.py raise RuntimeError(message) from e
- 3DMAx Panda Directx Exporter 导出 X插件
- 大数据资源争夺战此起彼伏,对用户而言是福是祸
- 赠书!《R语言数据分析与可视化从入门到精通》
- 蝴蝶效应 青蛙现象 鳄鱼法则 马太效应 木桶理论 二八定律(巴莱多定律) 破窗理论 羊群效应
- Word无法打开该文件,因为文件格式与文件扩展名不匹配 | 无法从该位置打开扩展名为.asd的文件
- 2021强网杯 Web赌徒 WP
- 哪款蓝牙耳机的音质好?四款音质最好的蓝牙耳机测评
- 全网最全关闭小米手机MIUI系统广告教程
- 几道js数组循环练习题
- 今天才发现,手机外放声音小,这样设置一下,轻松增大手机音量
- 水印相机定位不准确怎么办_云联相机app下载-云联相机app安卓版下载v1.0.0
- 期货中的“心静”和“身静”