简易聊天室

一、项目介绍

本项目实现了一个简易聊天室的基本功能,涵括

  • 利用vue框架实现注册、登录、聊天的前端页面展示以及与后端的交互逻辑。
  • 后端利用springboot处理前端的请求
  • 数据全部存储到了mysql数据库中,包括成员、消息等数据
  • 通过JWT校验处理前端的请求
  • 利用WebSocket向所有连接的成员发送信息

二、前端设计

1.前端登录界面设计

首先是较为简单的登录界面的设计。其中包含了简单的登录账号和登录密码的输入框,以及登录和注册的按键。当点击登录后会向后端提交账号密码,并且返回token,前端的页面会把token存储在pinia仓库内。以便后续请求的使用。

<template>
    <div class="background">
        <div class="head">
            <p>简易群聊</p>
        </div>
        <div class="login">
            <el-form ref="form">
                <el-form-item>
                    <el-input placeholder="请输入用户名" v-model="username" :prefix-icon="User"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-input placeholder="请输入密码" :prefix-icon="Lock" v-model="password"></el-input>
                </el-form-item>
            </el-form>
            <div class="anjian">
                <el-button :disabled="username=='' || password==''" type="primary" size="default" @click="login" >登录</el-button>
                <el-button type="primary" size="default" @click="goRegist">注册</el-button>
            </div>
            
        </div>
    </div>
</template>

前端界面的控制逻辑如下。

<script setup lang="ts">
import { Lock, User } from '@element-plus/icons-vue'
import { ref } from 'vue';
import request from '@/utils/request';
import {userStore} from '@/store/userStore';
import { ElMessage } from 'element-plus';
import {useRouter} from 'vue-router';
import {MyData} from '@/type/type'
const reqLogin= (data:any)=>request.post('/login',data)
let store = userStore();
let username = ref("");
let password = ref("")
let $router = useRouter()
const login=async()=>{
    let result:MyData = await reqLogin({username:username.value,password:password.value})
    if(result.code==200){
        store.userinfo = result.data
        $router.push("/index")
    }
    else{
        ElMessage({
            type: 'error',
            message: result.message,
        })
        
    }
}

const goRegist = ()=>{
    $router.push({path:'/register'})
}
</script>

2.注册页面设计

注册页面和登录页面很相似,主要就是输入注册的用户名和密码,多了一个上传头像的选项,向后端发送以后后端会返回相应的结果,若是成功,前端会跳转到。

<template>
    <div class="background">
        <div class="head">
            <p>注册</p>
        </div>
        <div class="register">
            <el-form ref="form">
                <el-form-item>
                    <el-input placeholder="请输入注册用户名" v-model="username" :prefix-icon="User"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-input placeholder="请输入注册密码" :prefix-icon="Lock" v-model="password"></el-input>
                </el-form-item>
                <el-form-item>
                    <span>上传头像:
                    </span>
                    <el-upload class="avatar-uploader" action="http://localhost:9998/upload" :show-file-list="false"
                        :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
                        <img v-if="imageUrl" :src="'http://localhost:9998'+imageUrl" class="avatar" />
                        <el-icon v-else class="avatar-uploader-icon">
                            <Plus />
                        </el-icon>
                    </el-upload>
                </el-form-item>
            </el-form>
            <div class="anjian">
                <el-button :disabled="username == '' || password == ''" type="primary" size="default"
                    @click="regist">确认注册</el-button>
                
            </div>

        </div>
    </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import request from '@/utils/request'; 
import type { UploadProps } from 'element-plus'
import {useRouter} from 'vue-router'
const $router = useRouter()
const reqRegist= (data:any)=>request.post('/regist',data)
const imageUrl = ref('')
let username = ref('')
let password = ref('')
const handleAvatarSuccess: UploadProps['onSuccess'] = (
    response,
    uploadFile
) => {
    
    imageUrl.value = response.message
    
}

const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
    if (rawFile.type !== 'image/png') {
        ElMessage.error('上传的图片必须是png格式!')
        return false
    }
    return true
}

const regist = async()=>{
    let result = await reqRegist({username:username.value,password:password.value,headUrl:imageUrl.value})
    if(result.code==200){
        ElMessage({type:"success",message:"注册成功,请重新登陆"})
        $router.replace({path:'/login'})
    }else{
        ElMessage({type:'error',message:result.message})
    }
}
</script>

3.聊天页面设计

聊天界面极简,仅包括所有注册聊天室的成员,消息展示框,发送消息功能以及退出登录功能。在挂载这个页面时进行成员信息获取、历史消息获取以及websocket连接。

<template>
    <div class="general">

        <div class="top">
            <div class="info">
                <img :src="'http://localhost:9998' + idToHead.get(myInfo.memberId)" />
                <span>{{ myInfo.memberName }}</span>
                <span class="quit" @click="logout">退出</span>
            </div>
        </div>
        <div class="container">
            <div class="left">
                <el-menu class="el-menu-vertical-demo" default-active="1">
                    <el-menu-item index="1">
                        <img :src="'http://localhost:9998' + idToHead.get(myInfo.memberId)" />
                        <span>{{ myInfo.memberName }}</span>
                    </el-menu-item>
                    <el-menu-item v-for="(item, no) in a" :key="item.memberId" v-show="item.memberId != myInfo.memberId"
                        :index="no + 2">
                        <img :src="'http://localhost:9998' + idToHead.get(item.memberId)" />
                        <span>{{ item.memberName }}</span>
                    </el-menu-item>
                </el-menu>
            </div>
            <div class="right">
                <div class="history">
                    <div class="message" v-for="(index, no) in message" :key="no">
                        <div class="head">
                            <img :src="'http://localhost:9998' + idToHead.get(index.memberId)" />
                            <span>{{ idToName.get(index.memberId) }}:</span>
                        </div>
                        <div class="xinxi"><span>{{ index.text }}</span></div>
                    </div>
                </div>
                <div class="input">
                    <textarea v-model="userInput"></textarea>
                </div>
                <div class="bottom" @click="sendMessage()">
                    <button>发送</button>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { userStore } from '@/store/userStore'
import type { MemList, Messages } from '@/type/type'
import request from '@/utils/request'
import { onMounted, ref } from 'vue';
let token = ref("")
let $router = useRouter()

window.onbeforeunload = function () {
    socket.close();
}
let userInput = ref("")
let store = userStore()
let socket: WebSocket = {} as WebSocket;
let myInfo = store.userinfo
onMounted(async () => {
    let t = await request.get("/member")
    a.value = t.data
    setProperty();
    t = await request.get("/message")
    message.value = t.data
    token.value = store.userinfo.token;
    socket = new WebSocket("ws://localhost:9998/ws/" + token.value)
    socket.onmessage = (event) => {
        message.value.push(JSON.parse(event.data))
    }
})

三、后端设计

这部分仅展示一些核心部分。

首先是登录,首先将前端传过来的密码经过MD5加密,再从数据库查询是否为已注册用户,若账号密码都正确,使用JWT生成token,返回给前端,作为之后登录的凭证,设置token的有效期为24小时。

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private LoginMapper loginMapper;
    @Autowired
    private JwtProperties jwtProperties;

    @Override
    public Result Login(LoginVo loginVo) {
        String password = DigestUtils.md5DigestAsHex(loginVo.getPassword().getBytes());
        loginVo.setPassword(password);
        UserInfo info = loginMapper.Login(loginVo);

        if(info==null){
            return Result.err(Result.CODE_ERR_BUSINESS,"账号或密码错误");
        }
        Map<String, Object> claims = new HashMap<>();
        claims.put("memberId", info.getMemberId());
        String token = JwtUtil.createJWT(
                jwtProperties.getSecretKey(),
                jwtProperties.getTtl(),
                claims);
        info.setToken(token);
        return Result.ok(info);
    }
}

接着就是对成员信息和历史消息的查询,采用mybatis框架对数据库进行操作。

 	@Select("select u.* from login_info l, user_info u where l.member_id=u.member_id and l.username = #{username} and l.password=#{password}")
    UserInfo Login(LoginVo loginVo);
   
    @Select("select * from user_info")
    List<UserInfo> queryMembers();

最关键的是websocket向各个在线用户发送消息,当收到信息后,JWT解析用户信息,遍历session列表,向每个连接的用户发送信息。

@OnMessage
    public void onMessage(String message, @PathParam("token") String token) {
        System.out.println("收到来自客户端:" + token + "的信息:" + message);
        Claims claims = JwtUtil.parseJWT(jwtProperties.getSecretKey(), token);
        Long memberId = Long.valueOf(claims.get("memberId").toString());
        Message message1 = new Message();
        message1.setSendTime(LocalDateTime.now());
        message1.setText(message);
        message1.setMemberId(memberId);
        messageService.addMessage(message1);
        sendToAllClient(message1);
    }
    
    public void sendToAllClient(Message message) {
        Collection<Session> sessions = sessionMap.values();

        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(JSON.toJSONString(message));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

最后展示一下注册方面的处理逻辑。首先就是查询有没有重复的用户名,然后将密码通过MD5加密,登录信息存入数据库,然后再存用户信息,主要是用户名和头像。

	@Transactional
    @Override
    public Result regist(RegisterVo registerVo) {
        String password = DigestUtils.md5DigestAsHex(registerVo.getPassword().getBytes());
        registerVo.setPassword(password);
        int n = registMapper.insertLoginInfo(registerVo);
        if(n==0){
            return Result.err(500,"用户名重复");
        }
        registMapper.insertUserInfo(registerVo);
        return Result.ok("注册成功");
    }

四、结果展示

在这里插入图片描述
在这里插入图片描述
下面登录两个账号,发送信息,二者都能收到。
在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值