(更新中)使用Vue+Node.js+Express+Socket.io+MySql实现完整版简单聊天室

github地址:https://github.com/DannyZeng2/EasyChat-Plus

1 使用技术

  • 前端 Vue 2.6 + ElementUI
  • 后端 Node.js 14.11.0 + Express 4.16.1
  • 消息推送 Socket.io 4.0
  • 数据存储 MySql 8.0.19存储用户信息,localStorage存储本地聊天记录

2 实现功能

  • 用户登录与注册 (已完成✔️)
  • 加入和离开群聊通知 (已完成✔️)
  • 群聊功能 (已完成✔️)
  • 发送文字信息 (已完成✔️)
  • 聊天记录本地存储 (已完成✔️)
  • 发送表情包 (已完成✔️)
  • 发送图片/视频 (未完成❌)
  • 私聊功能 (未完成❌)
  • 用户头像上传 (未完成❌)
  • 其他待定 (❔)

3 Demo 展示

3.1 登录与注册页面
登录

注册
3.2 聊天主页面
主页面1
主页面2

4 主要代码

4.1 创建表

CREATE TABLE `user` (
  `id` varchar(255) NOT NULL,
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `register_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) 

4.2 前端代码

Login.vue

<script>
export default {
  name: 'Login',
  data() {
    return {
      loginForm: {
        username: '',
        password: '',
      },
      registerForm: {
        username: '',
        password: '',
        phone: ''
      },
      checked: true,
      activeName: 'login',
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' }
        ],
        phone: [
          { required: true, message: '请输入手机号', trigger: 'blur' }
        ],
      }
    };
  },

  methods: {
    clickTab(tab) {
      if (tab.name == 'login') {
        this.clearRegisterFields()
      } else {
        this.clearLoginFields()
      }
    },

    clearRegisterFields() {
      this.registerForm.username = ''
      this.registerForm.password = ''
      this.registerForm.phone = ''
    },

    clearLoginFields() {
      this.loginForm.username = ''
      this.loginForm.password = ''
    },

    login(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.toChat()
        } else {
          return false;
        }
      });
    },

    toChat() {
      let _this = this
      let username = _this.loginForm.username
      let password = _this.loginForm.password

      let params = { username: username, password: password }
      this.$axios.get('/api/login', { params: params })
        .then(function (res) {
          if (res.data.success) {
            _this.$router.push({path: `/chat/${username}`})
            _this.$socket.emit('login', _this.loginForm.username)
          } else {
            _this.$message.error(res.data.message);
          }
        })
        .catch(function (err) {
          console.log(err);
        });
    },

    register(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.registerUser()
        } else {
          console.log('error submit!!');
          return false;
        }
      });

    },

    registerUser() {
      let _this = this
      let params = { username: this.registerForm.username, password: this.registerForm.password, phone: this.registerForm.phone }
      this.$axios.post('/api/register', null, { params: params })
        .then(function (res) {
          if (res.data.success) {
            _this.$message.success(res.data.message);
            _this.activeName = 'login'
            _this.loginForm.username = _this.registerForm.username
            _this.clearRegisterFields()
          } else {
            _this
            _this.$message.error(res.data.message);
          }
        })
        .catch(function (err) {
          console.log(err);
        });
    },
  }
}
</script>

Chat.vue

<script>
const appData = require("../static/emoji.json");
export default {
  name: 'Chat',
  data() {
    return {
      input: '',
      content: '',
      message: '',
      count:0,
      activeIndex: 1,
      userList:[],
      chatHistory:[],
      faceList: [],
      currentUser:this.$route.params.username
    }
  },
  mounted() {
    if(localStorage.getItem('chatHistory') ===null) {
      localStorage.setItem('chatHistory','')
    }else {
      this.chatHistory = JSON.parse(localStorage.getItem('chatHistory'))
    }
    for(let i in appData){
      this.faceList.push(appData[i].char);
    }
  },

  watch: {
    chatHistory() {
      this.$nextTick(() => {
        document.getElementById("content").scrollIntoView(false)
      })
    }
  },

  sockets: {
    connect() { },

    disconnect() {  },

    user_enter(data) {
      this.chatHistory.push(data)
      localStorage.setItem('chatHistory',JSON.stringify(this.chatHistory))
    },
    user_leave(data){
      this.chatHistory.push(data)
      localStorage.setItem('chatHistory',JSON.stringify(this.chatHistory))

    },
    count_users(data) {
      this.count = data.length
      this.userList = data
    },
    broadcast_msg(data) {
      this.chatHistory.push(data)
      console.log(this.chatHistory)
      localStorage.setItem('chatHistory',JSON.stringify(this.chatHistory))
    }
  },

  methods: {
    sendMsg() {

      if(this.input.trim() === '') {
        return
      }
      console.log(this.input)
      this.$socket.emit('send_msg', {
        username: this.currentUser,
        input: this.input.trim()
      })

      this.input = ''
    },
    getEmo(index){
      let selectionStart = document.getElementById('input').selectionStart;
      let start = this.input.substring(0,selectionStart)
      let end = this.input.substring(selectionStart+1)
      let emoji = this.faceList[index]

      this.input = start +emoji +end
    },
  }
}
</script>

4.3 后端代码

app.js

const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const http = require('http');
const app = express();
const server = http.createServer(app);
const socketIO = require('socket.io')
const io = socketIO(server, {
  cors: {
    origin: '*'
  }
});
const ws = require('./controller/websocket_controller');

ws.chat(io)

const indexRouter = require('./routes/index');

app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);\

server.listen('3000',() =>{
  console.log('正在监听port:3000...')
});

router/index.js

const express = require('express');
const router = express.Router();
const userController = require('../controller/user_controller');

router.get('/login', userController.login);
router.post('/register', userController.register);

module.exports = router;

db_config.js

const mysql = require('mysql')

module.exports = {
  config: {
    host: 'localhost',
    port: '3306',
    user: 'root',
    password: '123456',
    database: 'easychat'
  },

  getDBConnection(sql, params, callback) {
    let connection = mysql.createConnection(this.config);
    connection.connect();
    connection.query(sql, params, callback);
    connection.end();
  }
}

user_controller.js

const dbConfig = require('../utils/db_config')
const uuid = require('uuid');

login = (req, res) => {
  let username = req.query.username
  let password = req.query.password
  let params = [username, password]
  let sql = 'select username,password from user where username=? and password=?'
  console.log(sql)
  dbConfig.getDBConnection(sql, params, (error, results, fields) => {
    console.log(results)
    if (error) {
      res.send({ data: results, success: false, message: '登录失败!' })
    } else if (results.length === 0) {
      res.send({ data: results, success: false, message: '用户名或密码错误!' })
    } else {
      res.send({ data: results, success: true, message: '登录成功!' })
    }
  })
}

register = (req, res) => {
  console.log(req)
  let username = req.query.username
  let password = req.query.password
  let phone = req.query.phone
  let register_time = new Date()
  let params = [uuid.v1().toString(), username, password, phone, register_time]
  console.log(params)
  let sql = 'insert into user(id,username,password,phone,register_time) VALUES(?,?,?,?,?)'
  dbConfig.getDBConnection(sql, params, (error, results, fields) => {
    if (error) {
      res.send({ success: false, message: '注册失败!' })
    } else {
      res.send({ success: true, message: '注册成功!' })
    }
  })
}

module.exports = {
  login,
  register
}

websocket_controller.js

chat = (io) => {
  let count = 0
  let users = []
  io.on('connection', socket => {
    console.log('user connected')
    count++

    socket.on('login', (data) => {
      socket.username = data
      console.log(`${data}加入了聊天室`)
      const user = users.find(item => item === data)
      if (user) {
        socket.emit('loginError')
        console.log(user)
      }else {
        users.push(data)
        console.log(users)
        io.sockets.emit('user_enter', `${data}加入了聊天室`)
        io.sockets.emit('count_users', users)
      }
    })

    socket.on('send_msg', (data) => {
      console.log(`收到客户端的消息:${data}`)
      io.sockets.emit('broadcast_msg', {
        username: data.username,
        input: data.input,
        time: new Date().toLocaleString()
      })
    })

    socket.on('disconnect', () => {
      let index = users.findIndex(item => item === socket.username)
      users.splice(index,1)
      console.log('user disconnected')
      io.sockets.emit('user_leave', `${socket.username}离开了群聊`)
      io.sockets.emit('count_users', users)
    });
  });
}

module.exports = {
  chat
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容