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

news2024/11/18 18:50:07

目录

  • 一、项目简介
  • 二、环境介绍
  • 三、系统展示
  • 四、视频功能展示
  • 五、前端核心代码展示
  • 六、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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/54912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PowerShell禁止运行脚本

运行脚本报错(pnpm -v) pnpm : 无法加载文件 D:\win11\program\NVM\nodejs\pnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.mi crosoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。 所在位置 行:1 字符: 1pnpm -v…

洛谷千题详解 | P1019 [NOIP2000 提高组] 单词接龙【C++、Java语言】

博主主页&#xff1a;Yu仙笙 专栏地址&#xff1a;洛谷千题详解 目录 题目描述 输入格式 输出格式 输入输出样例 解析&#xff1a; C源码&#xff1a; Java源码&#xff1a; -----------------------------------------------------------------------------------------------…

java+mysql基于SSM共享型汽车租赁系统-计算机毕业设计

项目介绍 共享汽车租赁公司的共享汽车租赁流程复杂、数据庞大&#xff0c;往往一个疏忽就会给公司造成极大的损失&#xff0c;于是越来越多的共享汽车租赁公司需要一个对各项信息的管理平台来避免这样的损失。为了满足这个需求&#xff0c;我们开发一个针对共享汽车租赁信息的…

MR直播实例(混合现实直播)高品质企业直播

阿酷TONY / 2022-12-2 / 长沙 / 超多组图 绿幕抠像 虚拟场景&#xff08;三维场景&#xff09;实时渲染&#xff0c;降低直播成本&#xff0c;带来线下活动所没有的沉浸式视听体验。 虚拟舞台场景介绍参见&#xff1a; 企业年会直播来个虚拟舞台场景如何&#xff1f;_阿…

负载均衡与高可用

目录 负载均衡 理论部分 应用层负载均衡 环境搭建 代理服务器配置 web服务器配置 验证 网络层负载均衡 环境搭建 代理服务器配置 mysql服务器配置 验证 高可用 理论部分 环境搭建 负载均衡高可用 lb1主要服务器配置 lb2备份服务器配置 web配置 验证 nginx故障问题 →→→→ 大虾…

【MySQL】-增删查改

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀学Java的冬瓜&#x1f319; 专栏&#xff1a;MySQL 分享&#xff1a;至若春和景明&#xff0c;波澜不惊&#xff0c;上下天光&#xff0c;一碧万顷。沙鸥翔集&#xff0c;锦鳞游泳&#xff0c;岸芷汀兰&#xff0c;郁郁青青…

傻妞旧版合集新版订阅

目录一、前言二、下载三、新版傻妞订阅合集一、前言 傻妞旧版本(合集),包含amd和arm版本收集于TG 我的是amd架构 [rootecs-mike_note ~]# cat /proc/version Linux version 4.11.8-1.el7.elrepo.x86_64 (mockbuildBuild64F25) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-11…

Vue中的计算属性

计算属性&#xff1a;实际上是把vm中的属性进行计算加工&#xff0c;最后能够返回给页面一个结果 细想一下&#xff0c;其实methods方法也能实现1中描述的现象&#xff0c;但是计算属性最大的优势是缓存&#xff01;&#xff01;&#xff01; 举个例子 <div id"root&q…

【Android App】物联网实战项目之自动驾驶的智能小车(附源码和演示 超详细)

需要源码请点赞关注收藏后评论区留言私信~~~~ 当今社会正在步入一个万物互联的时代&#xff0c;它的技术基石主要来自5G、物联网和人工智能。 三者融合产生了许多新产品&#xff0c;其中最璀璨的当数自动驾驶的电动车&#xff1b;它汇聚了最新科技与工程实践的成果&#xff0c;…

【收纳】电脑资料-高效整理电脑上的文件

以前没想过整理文件&#xff0c;一般都是建各种文件夹&#xff0c;然后把资料拖进去&#xff0c;好像收起来了&#xff0c;但实际是随着文件越来越多&#xff0c;时间一久&#xff0c;经常找不到文件&#xff1b;后来逐渐意识到&#xff0c;需要改变。于是找了很多方法&#xf…

已分区的硬盘如何重新合并, 分出去的盘怎么重新合并

已分区的硬盘如何重新合并&#xff1f;磁盘分区后合区是指对原磁盘的分区进行合并&#xff0c;使之成为一个的磁盘分区&#xff0c;从具体的应用层面来分析&#xff0c;为什么会对磁盘分区后合区呢&#xff1f; 磁盘管理对磁盘分区后合区 在Windows系统中&#xff0c;磁盘管理…

UGUI性能优化学习笔记(二)合批

一、合批规则 合批&#xff1a;把渲染时使用相同材质、相同贴图的网格合并在一起&#xff0c;成为一个大网格&#xff0c;然后再调用一次Draw Call&#xff0c;直接渲染这一个大网格。这样做可以降低Draw Call的数量&#xff0c;以优化性能。 Unity是如何确定哪些网格可以进行…

[附源码]JAVA毕业设计货币博物馆展品管理系统(系统+LW)

[附源码]JAVA毕业设计货币博物馆展品管理系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项…

常用框架技术- 08 Spring Cloud简单易懂、易部署和易维护的分布式系统开发工具包

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1.微服务理论1.1 微服务的特点1.2 微服务与微服务架构1.3 微服务的优点2.分布式集群概念2.1 分布式集群概念2.1.1 什么是分布式2.1.2 什么是集群2.1.3 分布式集群…

新品上线 Naive Admin Tenant 开箱即用多租户开发框架

Naive Admin Tenant 是一套企业级的权限管理开发平台&#xff0c;采用前后端分离模式&#xff0c;微服务版本前端框架支持多个选择&#xff0c;支持数据库&#xff1a;MySql、Oracle、SqlServer、PostgreSql 等&#xff0c;目前只支持 MySql&#xff0c;后面有客户需求会扩展支…

三维数字沙盘大数据人工智能模拟对抗推演系统开发教程第一课

三维电子沙盘大数据人工智能模拟对抗推演系统开发教程第一课 该数据库中只提供 成都市火车南站附近的数据请注意&#xff0c;104.0648,30.61658 而且该公用服务器带宽不大&#xff0c;所以会有些卡顿&#xff0c;建议下载数据库后本地使用&#xff0c;下载后的数据库有些许变…

《InnoDB引擎八》InnoDB关键特性-两次写

InnoDB 关键特性 InnoDB存储引擎的关键特性包括&#xff1a; Insert Buffer (插入缓冲)Double Write (两次写)Adaptive Hash Index (自适应哈希索引)Async IO (异步IO)Flush Neighbor Page (刷新领接页) 这些特性为InnoDB存储引擎带来了更好的性能以及更高的可靠性。 两次写 …

STM32 定时器单脉冲模式的使用记录

一、我要解决的问题 我的需求 需要单片机几乎同时在A,B,C 三个IO 分别输出T1&#xff0c;T2&#xff0c;T3 时长的高电平&#xff0c;时间结束后&#xff0c;恢复低电平。 初步思路 面对这个需求&#xff0c;我第一时间想到的是用三个定时器&#xff0c;分别设置T1,T2,T3 时…

Win10解决:系统管理员已阻止你运行此应用

前言 Win10安装msi软件包时出现错误提示如下&#xff1a; 解决 按【winR】快捷键打开运行&#xff0c;输入gpedit.msc回车依次进入”Windows设置“—”安全设置“—”本地策略“–”安全选项“—”用户账户控制&#xff1a;以管理员批准模式运行所有管理员“—双击&#xf…

JUC系列(六) 线程池

&#x1f4e3; &#x1f4e3; &#x1f4e3; &#x1f4e2;&#x1f4e2;&#x1f4e2; ☀️☀️你好啊&#xff01;小伙伴&#xff0c;我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。 &#x1f4d2; 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️&#xff0c;擅…