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 聊天主页面
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
}