基于Vue+nodejs+Element-ui的聊天框项目

一、项目简介

本项目基于纯前端(移动端)技术开发一个聊天系统,界面美观大方,采用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

猜你喜欢

转载自blog.csdn.net/weixin_62897746/article/details/128150447