目录
- 一、项目简介
- 二、环境介绍
- 三、系统展示
- 四、视频功能展示
- 五、前端核心代码展示
- 六、MySQL 数据库创建功能展示
- 七、node.js 核心代码
- 八、总结
一、项目简介
本项目基于纯前端(移动端)技术开发一个聊天系统,界面美观大方,采用Nodejs+Vue+ElemenetUI开发实现,主要包含:登录注册,修改个人资料,更改头像,发送消息,单对单聊天等。
二、环境介绍
语言环境:nodejs
数据库:MySQL
应用服务器:nodejs
开发工具:vscode
开发技术:nodejs+vue+elementUI
三、系统展示
四、视频功能展示
基于Vue+nodejs+Element-ui的聊天系统项目
五、前端核心代码展示
- 头部导航功能代码
<template>
<div>
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="用户" name="user"><User></User></el-tab-pane>
<el-tab-pane label="我的" name="mine"><Mine></Mine></el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import User from "@/views/user/index.vue";
import Mine from "@/views/mine/index.vue";
export default {
components: {
User,
Mine,
},
data() {
return {
activeName: "Home",
};
},
mounted() {
this.activeName = this.$route.name;
},
methods: {
handleClick(tab, event) {
// console.log(this.activeName);
this.$router.push({
name: this.activeName,
});
},
},
};
</script>
<style></style>
- 登录注册页居中旋转功能代码
<template>
<div class="box" :class="{ box_rolling: isRolling }">
<div class="box_register"><register></register></div>
<div class="box_login"><login></login></div>
</div>
</template>
<script>
import login from "@/views/login/login.vue";
import register from "@/views/login/register.vue";
import { mapState } from "vuex";
export default {
components: {
login,
register,
},
computed: {
...mapState(["isRolling"]),
},
};
</script>
<style lang="scss" scoped>
.box {
&_register,
&_login {
transform-style: preserve-3d; //表示所有子元素在3D空间中呈现
backface-visibility: hidden; //元素背面向屏幕时是否可见
transition-duration: 0.5s;
transition-timing-function: "ease-in";
background: #008080;
}
&_login {
transform: rotateY(180deg);
visibility: hidden; //元素不可见,但占据空间
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
}
.box_rolling {
.box_register {
transform: rotateY(180deg);
visibility: hidden;
}
.box_login {
transform: rotateY(360deg);
visibility: visible;
}
}
</style>
- 登录页代码
<template>
<div class="box">
<p class="title">欢迎登录</p>
<input
type="text"
class="name"
placeholder="请输入账号"
v-model="username"
/>
<input
type="password"
class="password"
placeholder="请输入密码"
v-model="password"
/>
<button @click="add">登录</button>
<p class="login">
<span @click="addlogin">点击跳转到注册页</span>
</p>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
password: "",
};
},
methods: {
addlogin() {
this.$store.commit("addlogin");
},
add() {
this.$axios({
url: "/api/api/login",
method: "POST",
data: {
username: this.username,
password: this.password,
},
}).then(({ data }) => {
console.log(data);
localStorage.setItem("token", data.token);
location.href = "/";
});
},
},
};
</script>
<style lang="scss" scoped>
.box {
width: 100%;
height: 100vh;
padding: 0 0.5rem;
overflow: hidden;
text-align: center;
background-color: #f7f7f7;
.title {
font-size: 0.4rem;
text-align: center;
letter-spacing: 0.2rem;
margin: 0.2rem 0 0.5rem;
color: #7c7c7c;
}
.name,
.password,
.user {
width: 100%;
padding-left: 0.1rem;
height: 0.5rem;
border: 2px solid #cccccc;
border-radius: 0.2rem;
font-size: 0.15rem;
}
input::-webkit-input-placeholder {
color: #97040b;
}
.password {
margin-top: 0.75rem;
}
button {
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
background-color: #0b24fb;
border: none;
color: #fff;
font-size: 0.1rem;
margin-top: 0.5rem;
}
.login {
display: flex;
justify-content: flex-end;
margin-top: 0.3rem;
span {
font-size: 0.1rem;
color: #000;
font-weight: 400;
}
}
}
</style>
- 注册页代码
<template>
<div class="box">
<p class="title">欢迎注册</p>
<input
type="text"
class="name"
placeholder="请输入账号"
v-model="username"
/>
<input
type="password"
class="password"
placeholder="请输入密码"
v-model="password"
/>
<button @click="add">注册</button>
<p class="login">
<span @click="addlogin">点击跳转到登录页</span>
</p>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
password: "",
};
},
methods: {
addlogin() {
this.$store.commit("addlogin");
},
add() {
this.$axios({
url: "/api/api/reguser",
method: "POST",
data: {
username: this.username,
password: this.password,
},
}).then(({ data }) => {
console.log(data);
});
},
},
};
</script>
<style lang="scss" scoped>
.box {
width: 100%;
height: 100vh;
padding: 0 0.5rem;
overflow: hidden;
text-align: center;
background-color: #f7f7f7;
.title {
font-size: 0.4rem;
text-align: center;
letter-spacing: 0.2rem;
margin: 0.2rem 0 0.5rem;
color: #7c7c7c;
}
.name,
.password,
.user {
width: 100%;
padding-left: 0.1rem;
height: 0.5rem;
border: 2px solid #cccccc;
border-radius: 0.2rem;
font-size: 0.15rem;
}
input::-webkit-input-placeholder {
color: #97040b;
}
.password {
margin-top: 0.75rem;
}
button {
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
background-color: #0b24fb;
border: none;
color: #fff;
font-size: 0.1rem;
margin-top: 0.5rem;
}
.login {
display: flex;
justify-content: flex-end;
margin-top: 0.3rem;
span {
font-size: 0.1rem;
color: #000;
font-weight: 400;
}
}
}
</style>
- 我的主页代码
<template>
<div class="box">
<div class="top">
<img :src="imageUrl" class="avatar" />
<p>
{{ list.username }}
<br />
<router-link :to="{ name: 'setMessage' }">修改用户基本信息</router-link>
<span @click="tuichu">退出</span>
</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
imageUrl: "",
};
},
mounted() {
this.list = this.$store.state.userInfo;
this.imageUrl = this.list.image;
},
methods: {
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
},
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error("上传头像图片只能是 JPG 格式!");
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 2MB!");
}
return isJPG && isLt2M;
},
tuichu() {
localStorage.removeItem("token");
location.href = "/";
},
},
};
</script>
<style lang="scss" scoped>
.box {
.top {
display: flex;
.avatar {
width: 0.8rem;
height: 0.8rem;
display: block;
}
p {
margin-left: 0.2rem;
font-size: 0.25rem;
a {
text-decoration: none;
font-size: 0.1rem;
color: gray;
margin-right: 0.2rem;
}
}
}
}
</style>
- 修改个人信息代码
<template>
<div class="box">
<div>
<el-upload
class="avatar-uploader"
action="/api/api/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<p>
用户名:
<el-input v-model="input" :placeholder="list.username"> </el-input>
</p>
<p>
个人简介:
<el-input
type="textarea"
:rows="2"
placeholder="请输入内容"
v-model="textarea"
>
</el-input>
</p>
<el-button type="primary" @click="add">提交</el-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
imageUrl: "",
input: "",
textarea: "",
image: "",
};
},
mounted() {
this.list = this.$store.state.userInfo;
this.imageUrl = this.list.image;
this.input = this.list.username;
this.textarea = this.list.brief;
},
methods: {
handleAvatarSuccess(res, file) {
// console.log(res, file.name);
this.image = file.name;
this.imageUrl = URL.createObjectURL(file.raw);
},
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error("上传头像图片只能是 JPG 格式!");
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 2MB!");
}
return isJPG && isLt2M;
},
add() {
this.$axios({
url: `/api/my/setmessages`,
method: "post",
data: {
image: this.image
? "http://127.0.0.1:830/api/uploads?img=image_" + this.image
: this.list.image,
username: this.input,
brief: this.textarea,
},
headers: {
Authorization: localStorage.getItem("token"),
},
}).then(({ data }) => {
console.log(data);
location.href = "/footer/mine";
});
},
},
};
</script>
<style lang="scss" scoped>
.box {
div {
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
}
}
</style>
- 聊天框内容代码
<template>
<div class="box">
<div class="top">
<i class="el-icon-caret-left" @click="go"></i>
<span>{{ list.username }}</span>
<img :src="list.image" alt="" />
</div>
<ul class="center" ref="center">
<li
v-for="item in messages"
:key="item.id"
:class="{ userId: item.user_id === userinfo.user_id }"
>
<img :src="list.image" alt="" v-if="item.user_id === list.user_id" />
<p>
<span v-if="item.user_id === list.user_id" class="title"
>{{ list.username }} <br
/></span>
<span class="content">{{ item.content }}</span>
</p>
<img
:src="userinfo.image"
alt=""
v-if="item.user_id !== list.user_id"
/>
</li>
</ul>
<div class="bottom">
<input
type="text"
placeholder="请输入内容"
v-model="input"
@keydown.enter="send"
class="inp"
/>
<button class="btn" @click="send">发送(S)</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
input: "",
messages: [],
userinfo: [],
timeout: "",
};
},
mounted() {
this.userinfo = this.$store.state.userInfo;
// console.log(this.userinfo);
// console.log(this.$route.query.userid);
let { userid } = this.$route.query;
// console.log(userid);
this.$axios({
url: `/api/my/userid?id=${userid}`,
method: "get",
headers: {
Authorization: localStorage.getItem("token"),
},
}).then(({ data }) => {
// console.log(data);
this.list = data.list;
});
var i = 0;
this.timeout = setInterval(() => {
i++;
this.$nextTick(() => {
// console.log(this.$refs.center.scrollHeight);
this.$refs.center.scrollTop = this.$refs.center.scrollHeight;
});
if (i > 3) {
clearInterval(this.timeout);
}
}, 500);
},
destroyed() {
clearInterval(this.timeout);
},
watch: {
messages: {
handler() {
this.gets();
},
immediate: true,
},
},
methods: {
go() {
this.$router.go(-1);
},
send() {
let { userid } = this.$route.query;
if (this.input !== "") {
this.$axios({
url: `/api/my/publish`,
method: "post",
headers: {
Authorization: localStorage.getItem("token"),
},
data: {
content: this.input,
receive_id: userid,
},
}).then(({ data }) => {
this.messages = data.list;
// console.log(data);
this.input = "";
this.$nextTick(() => {
this.$refs.center.scrollTop = this.$refs.center.scrollHeight;
});
});
} else {
this.$message({
message: "发布消息不能为空!",
type: "warning",
});
}
},
gets() {
let { userid } = this.$route.query;
this.$axios({
url: `/api/my/messages?display=${userid}`,
method: "get",
headers: {
Authorization: localStorage.getItem("token"),
},
}).then(({ data }) => {
// console.log(data);
this.messages = data.list;
});
},
},
};
</script>
<style lang="scss" scoped>
.box {
height: 100vh;
.top {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 0.5rem;
padding: 0.05rem 0.1rem;
background-color: gainsboro;
display: flex;
align-items: center;
justify-content: space-between;
i {
font-size: 0.2rem;
}
img {
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
}
}
.center {
padding: 0.5rem 0 0.4rem 0;
height: 100vh;
overflow: auto;
background-color: #f5f5f5;
li {
list-style: none;
width: 100%;
display: flex;
align-items: center;
padding: 0 0.05rem;
p {
margin: 0.1rem;
.title {
font-size: 0.1rem;
color: gray;
}
.content {
padding: 0.02rem 0.05rem;
background-color: #fff;
border-radius: 0.05rem;
}
}
img {
width: 0.3rem;
height: 0.3rem;
border-radius: 50%;
}
}
.userId {
justify-content: flex-end;
}
}
.bottom {
width: 100vw;
position: fixed;
left: 0;
bottom: 0;
display: flex;
.inp {
width: 2.55rem;
padding: 0.1rem;
border: 1px solid gainsboro;
}
.inp:focus {
outline: none;
}
.btn {
width: 1rem;
padding: 0 0.1rem;
box-sizing: content-box;
border: none;
background-color: rgb(182, 151, 151);
color: rgb(0, 155, 39);
margin: 0;
}
}
}
</style>
- 好友页代码
<template>
<div class="box">
<ul>
<li v-for="item in list" :key="item.id" @click="add(item)">
<img :src="item.image" alt="" />
<p>{{ item.username }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
};
},
mounted() {
this.$axios({
url: "/api/my/userall",
method: "get",
headers: {
Authorization: localStorage.getItem("token"),
},
}).then(({ data }) => {
console.log(data);
this.list = data.list;
});
},
methods: {
add(item) {
// console.log(item);
this.$router.push({
name: "chat",
query: { userid: item.user_id },
});
},
},
};
</script>
<style lang="scss" scoped>
.box {
ul {
li {
list-style: none;
display: flex;
align-items: center;
padding: 0.1rem 0.1rem;
border-bottom: 1px solid gainsboro;
img {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
border: 1px solid red;
margin-right: 0.1rem;
color: #333;
}
}
li:hover {
background-color: gainsboro;
}
}
}
</style>
- router 路由页代码
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '@/components/home'
import axios from 'axios'
import store from '@/store/index'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
redirect: 'footer',
component: HomeView,
meta: {
isLogn:true
}
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index'),
},
{
path: '/footer',
name: 'footer',
redirect: {name:'user'},
component: () => import('@/views/home/footer.vue'),
meta: {
isLogn:true
},
children: [
{
path: 'user',
name: 'user',
component: () => import('@/views/user/index.vue'),
meta: {
isLogn:true
}
},
{
path: 'mine',
name: 'mine',
component: () => import('@/views/mine/index.vue'),
meta: {
isLogn:true
}
},
]
},
{
path: '/chat',
name: 'chat',
component: () => import('@/views/chat/index.vue'),
meta: {
isLogn:true
}
},
{
path: '/setMessage',
name: 'setMessage',
component: () => import('@/views/mine/setMessage.vue'),
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const islogin = !!token
// console.log(token, islogin)
var data = ''
axios({
url: `/api/my/myuser`,
method: "get",
headers: {
Authorization: localStorage.getItem("token"),
},
}).then(({ data }) => {
store.commit('userInfo', data.list)
if (to.matched.some((item) => item.meta.isLogn)) {
if (data.status === 1) {
localStorage.removeItem('token')
window.location.href = '/login'
} else {
if (islogin) {
next()
} else {
// 没有跳转至登录
window.location.href = '/login'
}
}
} else {
if (islogin && to.path === '/login') {
// 跳转至首页
window.location.href = '/'
return
}
next()
}
})
})
export default router
- vuex 页代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userInfo: [],
isRolling: false,
},
getters: {
isLogin(userInfo) {
return !!userInfo.username
}
},
mutations: {
addlogin(state) {
state.isRolling = !state.isRolling
},
userInfo(state, data) {
state.userInfo = data
}
},
actions: {
},
modules: {
}
})
- package.json 页代码
{
"name": "chat_vue",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^1.1.3",
"core-js": "^3.8.3",
"element-ui": "^2.15.12",
"vue": "^2.6.14",
"vue-router": "^3.5.1",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"vue-template-compiler": "^2.6.14"
}
}
- 移动端代码
const WIDTH = 375//如果是尺寸的设计稿在这里修改
const setView = () => {
//设置html标签的fontSize
document.documentElement.style.fontSize = (100 * document.documentElement.clientWidth / WIDTH) + 'px'
}
window.onresize = setView
setView()
- main.js页面代码
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'axios'
import '@/rem/index'
Vue.prototype.$axios = axios
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
六、MySQL 数据库创建功能展示
- 用户信息表
- 聊天发布内容表
七、node.js 核心代码
- 根页面
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())
// 配置解析表单数据的中间件
app.use(express.json()) // 解析 json 格式
// 注意:这个中间件,只能解析 application/x-www-form-urlencoded 格式的表单数据
app.use(express.urlencoded({ extended: false }))
// 响应数据的中间件
app.use(function (req, res, next) {
// status = 0 为成功; status = 1 为失败; 默认将 status 的值设置为 1,方便处理失败的情况
res.cc = function (err, status = 1) {
res.send({
// 状态
status,
// 状态描述,判断 err 是 错误对象 还是 字符串
message: err instanceof Error ? err.message : err,
})
}
next()
})
const joi = require('@hapi/joi')
// 错误中间件
app.use(function (err, req, res, next) {
// 数据验证失败
if (err instanceof joi.ValidationError) return res.cc(err)
// 未知错误
res.cc(err)
})
// 导入配置文件
const config = require('./config')
// 解析 token 的中间件
const expressJWT = require('express-jwt')
// 使用 .unless({ path: [/^\/api\//] }) 指定哪些接口不需要进行 Token 的身份认证
app.use(expressJWT({ secret: config.jwtSecretKey }).unless({ path: [/^\/api\//] }))
// 错误中间件
app.use(function (err, req, res, next) {
// 捕获身份认证失败的错误
if (err.name === 'UnauthorizedError') return res.cc('身份认证失败!')
})
// 导入并注册用户路由模块
const userRouter = require('./router/user')
app.use('/api', userRouter)
// 导入并使用用户信息路由模块
const userallRouter = require('./router/userall')
// 注意:以 /my 开头的接口,都是有权限的接口,需要进行 Token 身份认证
app.use('/my', userallRouter)
app.listen(0830, function () {
console.log('api server running at http://127.0.0.1:0830')
})
- 接口页面
不加密
// 导入 express 模块
const express = require('express')
// 创建路由对象
const router = express.Router()
const multiparty = require('multiparty')
var multer = require('multer')
const fs = require("fs");
// 导入用户路由处理函数模块
const userHandler = require('../router_handler/user')
router.post('/reguser',userHandler.reguser)
router.post('/login',userHandler.login)
router.get('/uploads', userHandler.image)
// 单图上传
router.post(
"/upload",
multer({
//设置文件存储路径
dest: "public/image",
}).array("file", 1),
function (req, res, next) {
let files = req.files;
let file = files[0];
let fileInfo = {};
let path = "public/image/" + 'image_' + file.originalname;
fs.renameSync("./public/image/" + file.filename, path);
//获取文件基本信息
fileInfo.type = file.mimetype;
fileInfo.name = file.originalname;
fileInfo.size = file.size;
fileInfo.path = path;
res.json({
code: 200,
msg: "OK",
data: fileInfo,
});
console.log(fileInfo.path)
}
);
// 将路由对象共享出去
module.exports = router
加密:
// 导入 express
const express = require('express')
// 创建路由对象
const router = express.Router()
// 导入用户路由处理函数模块
const userAll = require('../router_handler/userall')
// 获取所有用户
router.get('/userall',userAll.getUserall)
// 获取指定用户
router.get('/userid', userAll.getUserid)
// 获取当前登录用户
router.get('/myuser', userAll.getMyuser)
// 发布消息
router.post('/publish', userAll.setpublish)
// 获取消息
router.get('/messages', userAll.getMessage)
// 更改用户信息
router.post('/setmessages', userAll.setMessage)
// 向外共享路由对象
module.exports = router
- 接口内容页面
不加密:
const db = require('../db/index')
// 导入 bcryptjs 插件(对密码进行加密)
const bcrypt = require('bcryptjs')
// 导入 path 用来显示图片
var path = require('path');
// 用这个包来生成 Token 字符串
const jwt = require('jsonwebtoken')
// 导入全局的配置文件
const config = require('../config')
// 注册用户的处理函数
exports.reguser = (req, res,next) => {
// 接收表单数据
const userinfo = req.body
// 判断数据是否合法
if (!userinfo.username || !userinfo.password) {
return res.send({ status: 1, message: '用户名或密码不能为空!' })
}
const sql = `select * from users where username=?`
db.query(sql, [userinfo.username], function (err, results) {
// 执行 SQL 语句失败
if (err) {
return res.send({ status: 1, message: err.message })
}
// 用户名被占用
if (results.length > 0) {
return res.send({ status: 1, message: '用户名被占用,请更换其他用户名!' })
}
// 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
const articleInfo = {
// 标题、内容、状态、所属的分类Id
...req.body,
// 文章封面在服务器端的存放路径
image: 'http://127.0.0.1:830/api/uploads?img=1669116817314_21.jpg',
// 账号创建时间
time: new Date(),
// 作者的Id
user_id: Math.floor(Math.random()*(9999999999-100000)+100000)
}
// console.log(articleInfo)
const sql = 'insert into users set ?'
db.query(sql, articleInfo, function (err, results) {
// 执行 SQL 语句失败
if (err) return res.send({ status: 1, message: err.message })
// SQL 语句执行成功,但影响行数不为 1
if (results.affectedRows !== 1) {
return res.send({ status: 1, message: '注册用户失败,请稍后再试!' })
}
// 注册成功
res.send({ status: 0, message: '注册成功!' })
})
})
}
// 登录的处理函数
exports.login = (req, res) => {
const userinfo = req.body
const sql = `select * from users where username=?`
db.query(sql, userinfo.username, function (err, results) {
// 执行 SQL 语句失败
if (err) return res.cc(err)
// 执行 SQL 语句成功,但是查询到数据条数不等于 1
if (results.length !== 1) return res.cc('登录失败!')
// TODO:判断用户输入的登录密码是否和数据库中的密码一致
// 拿着用户输入的密码,和数据库中存储的密码进行对比
const compareResult = bcrypt.compareSync(userinfo.password, results[0].password)
// 如果对比的结果等于 false, 则证明用户输入的密码错误
if (!compareResult) {
return res.cc('登录失败!')
}
// 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值
const user = { ...results[0], password: '', user_pic: '' }
// 对用户的信息进行加密,生成 Token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiresIn, // token 有效期为 72 个小时
})
res.send({
status: 0,
message: '登录成功!',
// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀
token: 'Bearer ' + tokenStr,
})
})
}
// 显示图片
exports.image = (req, res) => {
// console.log(__dirname)
// res.sendFile(path.join(__dirname, 'public/image'));
res.sendFile(path.join(process.cwd(), '/public/image/' + req.query.img));
// res.send('image ok')
// console.log(process.cwd(),__dirname)
}
加密:
const db = require('../db/index')
// 获取所有用户
exports.getUserall = (req, res) => {
const sql = `select * from users where user_id!=?`
// console.log(req.user.user_id)
db.query(sql, req.user.user_id,function (err, results) {
// console.log(err, results)
if(err) return res.cc(err)
res.send({
status: 0,
message: '获取所有用户信息成功',
list: results,
})
})
// res.send('ok')
}
// 获取指定用户
exports.getUserid = (req, res) => {
const userinfo = req.url.split('=')
const user_id = userinfo[userinfo.length - 1]
const sql = `select * from users where user_id=?`;
db.query(sql, user_id, (err, results) => {
// 1. 执行 SQL 语句失败
if (err) return res.cc(err)
// 2. 执行 SQL 语句成功,但是查询到的数据条数不等于 1
if (results.length !== 1) return res.cc('获取用户信息失败!')
// 3. 将用户信息响应给客户端
res.send({
status: 0,
message: '获取用户基本信息成功!',
list: results[0],
})
})
}
// 获取当前用户信息
exports.getMyuser = (req, res) => {
const sql = `select * from users where user_id=?`;
db.query(sql, req.user.user_id, function (err, results) {
if (err) return res.cc(err)
if (results.length !== 1) return res.cc('获取用户信息失败!')
// 将用户信息响应给客户端
res.send({
status: 0,
message: '获取当前登录用户信息成功!',
list: results[0],
})
})
}
// 发布消息
exports.setpublish = (req, res) => {
// console.log(req.body.receive_id)
// 接收表单数据
const articleInfo = {
// 发布内容
...req.body,
// 信息发布时间
time: new Date(),
// 发布者 id
user_id: req.user.user_id,
display_position: req.user.user_id + '' + req.body.receive_id
}
// console.log(articleInfo)
const sql = `insert into chat_table set ?`
db.query(sql, articleInfo,function (err, results) {
// 执行 SQL 语句失败
if (err) return res.send({ status: 1, message: err.message })
// SQL 语句执行成功,但影响行数不为 1
if (results.affectedRows !== 1) {
return res.send({ status: 1, message: '发布消息失败,请稍后再试!' })
}
// 注册成功
// res.send({ status: 0, message: '发布成功!', list: req.body.content })
// console.log(req.body.receive_id,req.user.user_id)
const sql = `select * from chat_table where state=0 and display_position=${req.user.user_id + '' + req.body.receive_id} or display_position=${req.body.receive_id + '' + req.user.user_id}`;
db.query(sql, 0,function (err, results) {
// 1. 执行 SQL 语句失败
if (err) return res.cc(err)
// 3. 将用户信息响应给客户端
res.send({
status: 0,
message: '发布成功!',
list: results,
})
})
})
}
// 获取指定信息
exports.getMessage = (req, res) => {
const userinfo = req.url.split('=')
const user_id = userinfo[userinfo.length - 1]
const sql = `select * from chat_table where state=0 and display_position=${req.user.user_id + '' + user_id} or display_position=${user_id + '' + req.user.user_id}`;
db.query(sql, 0,function (err, results) {
// 1. 执行 SQL 语句失败
if (err) return res.cc(err)
// 3. 将用户信息响应给客户端
res.send({
status: 0,
message: '获取信息成功!',
list: results,
})
})
}
// 更改用户信息
exports.setMessage = (req, res) => {
console.log(req.body,req.user.user_id)
const sql = `update users set ? where user_id=?`
db.query(sql, [req.body,req.user.user_id], (err, results) => {
// 执行 SQL 语句失败
console.log(err)
if (err) return res.cc(err)
// 执行 SQL 语句成功,但影响行数不为 1
// if (results.affectedRows !== 1) return res.cc('修改用户基本信息失败!')
// 修改用户信息成功
return res.cc('修改用户基本信息成功!', 0)
})
}
- 验证规则页面
const joi = require('joi')
/**
* string() 值必须是字符串
* alphanum() 值只能是包含 a-zA-Z0-9 的字符串
* min(length) 最小长度
* max(length) 最大长度
* required() 值是必填项,不能为 undefined
* pattern(正则表达式) 值必须符合正则表达式的规则
*/
// 用户名的验证规则
const username = joi.string().min(1).max(10).required()
// 密码的验证规则
// const password = joi.string().min(1).max(50).required()
// 头像
const image = joi.string()
// 用户 id 的验证规则
const user_id = joi.number().integer().min(1)
// 状态
const state = joi.string().valid('0', '1')
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
// 表示需要对 req.body 中的数据进行验证
body: {
username,
// password,
image,
user_id,
state
},
}
- 链接数据库页面
// 导入 mysql 模块
const mysql = require('mysql')
// 创建数据库连接对象
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'wang20030830',
database: 'chat',
})
// 向外共享 db 数据库连接对象
module.exports = db
- 加密页面
// 这是一个全局的配置文件
module.exports = {
// 加密和解密 Token 秘钥
jwtSecretKey: 'wangshihao No1. ^_^',
// Token 的有效期
expiresIn: '72h'
}
- package.json 页面
{
"name": "chat_node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@escook/express-joi": "^1.1.1",
"@hapi/joi": "^17.1.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-jwt": "^5.3.3",
"fs": "^0.0.1-security",
"joi": "^17.7.0",
"jsonwebtoken": "^8.5.1",
"multer": "^1.4.5-lts.1",
"multiparty": "^4.2.3",
"mysql": "^2.18.1",
"path": "^0.12.7"
}
}
八、总结
以上就是 聊天框项目的所有功能简介和代码,不懂得可以在评论区里问我或私聊我询问,以后会持续发布一些新的功能,敬请关注。
我的其他文章:https://blog.csdn.net/weixin_62897746?type=blog