Vue+Express.js+Socket.io+axios聊天室实现

或许是小光 2022-01-18 14:19:44

目录

  • 系统设计
  • Vue前端
  • 页面结构与路由
  • 个人信息
  • 聊天室
  • Express后端
  • 跨域Session支持
  • 静态数据
  • 中间件
  • 接口定义

系统设计

本文旨在设计一个匿名的单房间聊天室应用,使用MVVM架构,在前后端分离的情况下进行系统测试。
本文采用Vue作为Web前端框架,Express.js作为后端框架,采用Socket.io提供长连接通信。
前端使用ElementUI作为组件库,以下是完成效果展示:

聊天室界面:

img

个人信息界面(可以修改昵称):

img

联系作者界面:

img

即时通讯:

img

Vue前端

页面结构与路由

该项目前端页面分为顶部导航条、中部功能区、下部功能区两部分,为了实现不同页面的功能区变化,本文采用了router-view。
此处通过Element-UI提供的容器分割了页面,并使用两个router-view来对应地改变功能区内容。其中由于中下功能区并非一一对应关系,为了提高复用性,分为两个view。
其中Navigation组件为自定义组件,用于实现顶部导航条并进行跳转。

<template>
  <div class="window" id="app">
    <el-container>
        <el-header>
          <navigation></navigation>
        </el-header>
        <el-main>
          <router-view name="default"></router-view>
        </el-main>
        <el-footer>
          <router-view name="footer"></router-view>
        </el-footer>
    </el-container>
  </div>
</template>
<template>
    <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
        <el-menu-item index="/chat_room/default">
            会话窗口
        </el-menu-item>
        <el-menu-item index="/user_info">
            个人信息
        </el-menu-item>
        <el-menu-item index="/contact_us">
            联系作者
        </el-menu-item>
    </el-menu>
</template>

随后在main.js中添加路由信息,并注册组件。

const routes = [
  { path: '/chat_room/:room_id',
    components: {
      default: ChatRoom,
      footer: ChatFooter
    } 
  },
  { path: '/user_info',
    components: {
      default: UserInfo,
      footer: PlainFooter
    } 
  },
  { path: '/contact_us',
    components:{
      default: ContactUs,
      footer: PlainFooter
    }
  }
]

为了提供导航功能,采用Vue-router库提供的路由功能,代码如下:

export default {
    name: 'Navigation',
    data() {
        return {
        activeIndex: '1',
        };
    },
    methods: {
        handleSelect(key, keyPath) {
            this.activeIndex = key;
            console.log(key, keyPath);
            this.$router.replace(key)
        }
    }
}

个人信息

个人信息部分支持修改昵称,昵称保存于Session中,前端部分仅需要使用axios发起HTTP请求以修改昵称或是获取昵称。

 getName() {
            this.$axios.get('http://localhost:3000/get_name')
          .then(response => {
            this.form.name = response.data;
          })
          .catch(error => {
            console.log(error);
          })
        },
        setName() {
          this.$axios.post('http://localhost:3000/set_name', {user_name: this.form.name}
          )
          .then(response => {
            this.form.name = response.data;
          })
          .catch(error => {
            console.log(error);
          })
        }

页面设计部分使用了Element-UI提供的表单组件,绑定了刷新昵称和修改昵称两个事件,通过Vue提供的MVVM容器进行了双向绑定,所有的修改和刷新均连接至页面Vue实例的form对象中。

<template>
    <el-card class="box-card">
        <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="昵称">
                <el-input id="name" v-model="form.name"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="setName">修改昵称</el-button>
                <el-button type="secondary" @click="getName">刷新昵称</el-button>
            </el-form-item>
        </el-form>
    </el-card>
</template>
data() {
        return {
          form: {
          name: ''
          }
        };
    },

聊天室

聊天室内采用了Element-UI提供的卡片组件,使用Vue数据绑定+轮询生成HTML的方式进行显示以及实时信息显示。

<template>
    <ul id="chat_record">
        <li v-for="record in records" :key="record.time">
            <el-card class="box-card">
                <div slot="header" class="clearfix">
                    <span>{{record.user}}</span>
                </div>
                <div class="text item">
                    {{record.content}}
                </div>
            </el-card>
        </li>
    </ul>
</template>

establish_connection(){
  const socket = this.$io("http://localhost:3000")
  socket.on("connection", (arg) => {
    console.log(arg); // world
  });
  socket.on("newMessage", (arg) =>{
    console.log(arg); // world
    this.records.push(arg)
  });
},
get_message(room_id){
    if(!room_id)room_id = "default"
    this.$axios.get('http://localhost:3000/get_data?room_id=' + room_id)
        .then(response => {
        this.records = response.data;
        })
        .catch(error => {
        console.log(error);
        })
}

进入该页面时,axios将负责首次的消息显示,随后Socket.io将会尝试连接至服务器,并且在得到消息时由服务端负责通知页面,以实现实时通信的功能。
该页面同时还具有一个独立的底部功能区路由视图,提供了发送消息的功能。使用的是Element-UI提供的单行表单组件,以及axios的post功能。

使用Vue-socket.io时发生了无法修正的bug,因而采用原生socket.io进行连接

<template>
    <el-form :inline="true" :model="formInline" class="demo-form-inline">
        <el-form-item label="消息内容">
            <el-input v-model="formInline.content" placeholder="发一条友善的消息吧"></el-input>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="onSubmit">发送</el-button>
        </el-form-item>
    </el-form>
</template>
onSubmit() {
            this.$axios.post('http://localhost:3000/send_message?room_id=' + this.$route.params.room_id, {content: this.formInline.content}
            )
            .then(response => {
                this.formInline.content = ""
                console.log(response);
            })
            .catch(error => {
                console.log(error);
            })
        }

在发送成功后将清空输入框,以通知用户已发送成功。失败时将在console给出提示。

Express后端

本项目后端部分使用Express制作,需要提供以下功能:
跨域支持、消息获取、即时消息获取、消息发送、昵称修改、昵称获取。

跨域Session支持

本项目使用CORS(Cross-origin resource sharing)进行跨域配置。
为了允许携带cookie信息,不可以将origin设置为*****,否则将提示无法在全通配符地址上进行跨域的认证。

var corsOptions = {
    origin: "http://localhost:8080",
    credentials: true,
    optionsSuccessStatus: 200
}

app.use(cors(corsOptions))

这样的设置可以允许站点携带cookie等信息进行通讯,以支持基于session的昵称系统。

为了提供session功能,我们需要对服务器进行配置。

app.use(session({
    secret: 'sec',
    resave: true,
    saveUninitialized: true
}))

静态数据

为方便测试,本站点提供了一小段静态数据,在服务器开启时就保存在内存中。

data = {
    "default" :
    [
        {time : "0",
        user : "Lighty",
        content : "Hello!"},
        {time : "1",
        user : "TinyBookE",
        content : "?"}
    ]
}

中间件

为了支持Json的数据格式,我们加入了对应的中间件。

app.use(express.json())

接口定义

以下代码定义了服务端的各个接口:

app.post('/set_name', (req, res) => {
    req.session.user_name = req.body.user_name
    res.send(req.session.user_name)
})

app.get('/get_name', (req, res) => {
    res.send(GetUserName(req))
})

app.get('/get_data', (req, res) => {
    console.log(req.query)
    if(req.query.room_id){
        res.send(data[req.query.room_id])
    }
    else res.send(data["default"])
})

app.post('/send_message', (req, res) => {
    console.log(req.query)
    if(req.body.content.trim().length == 0){
        res.send("0")
        return
    }
    var newMessage = {
        time: next_time++,
        user: GetUserName(req),
        content: req.body.content
    }
    console.log(newMessage)
    data[req.query.room_id].push(newMessage)
    res.send("0")
    io.emit("newMessage", newMessage)
})

function GetUserName(req){
    if(!req.session.user_name)req.session.user_name="Unknown User"
    return req.session.user_name;
}

可以看到,用户的昵称等信息完全保存于session中,服务端会根据从request body中获得的信息来对其进行更新。在名字不存在、非法或未设置时将使用Unknown User作为占位符使用。
用户可以使用get_data接口一次性获取整个信息,也可以依靠socket.io建立的长连接来获取新的信息内容,此时新信息将携带下标,以便插入到前端的数据集中。
用户指明room_id之后可以上传信息,信息将和下标、发送者昵称一起组织为对象,保存在对应房间的内存中。随后服务端将通过socket.io发送消息到所有客户端以即时更新信息。

NP258

...全文
1266 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧