在用户注册时,先发送邮件得到验证码.后端将验证进行缓存比对,如果验证码到期,比对不正确,拒绝登录;如果比对正确,将用户的信息进行加密存储到数据库.
用户登录时,先通过用户名去数据库查询是否存在该用户,如果不存在发送信息提示前端,如果存在.将用户输入的密码和将数据库中解密成功后的密码进行比对.如果成功,将用户的唯一id生成token与用户的信息一起发送给前端.前端将token存入Cookie中.
在一定时间内,用户第二次登录时.先去获取Cookie,将token解析出来,发送给后端,后端进行token解密,得到用户的id,通过id去数据库查询用户数据,并返回给前端.
推荐看大佬写的
版本推荐4以上,这里使用的是4.4.23
配置好后,我们创建一个数据库,为这个数据库添加一个用户.
db.createUser({user:"用户名", pwd: "用户密码", roles: [{role: "dbOwner", db: "数据库名"}]})
我们接下来连接这个数据库.
npm i mongoose
- const mongoose = require('mongoose');
-
- const connect='mongodb://数据库用户名:数据库用户密码@127.0.0.1:27017/数据库名'
- mongoose.connect(connect, {
- useNewUrlParser: true,
- useUnifiedTopology: true,
- });
- //创建实例
- const db = mongoose.connection;
- //数据库连接
- db.on('error', console.error.bind(console, '连接错误:'));
- db.once('open', function () {
- console.log('成功连接到数据库');
- });
-
- //定义用户模型
- ...
-
- // 关闭连接(在程序退出时)
- process.on('SIGINT', async function () {
- try {
- await mongoose.connection.close();
- console.log('数据库连接已关闭');
- process.exit(0);
- } catch (error) {
- console.error('关闭数据库连接时出错:', error);
- process.exit(1);
- }
- });
-
- module.exports = {db}
用户信息包括
_id:数据库自动生成
username:用户名,唯一不可出现同样的用户名,
email:邮箱名,唯一不可出现同样的邮箱名,
password:密码,加密
...其他属性可以自定义添加
安装bcrypt,一种加密算法
npm i bcrypt
- ...连接
-
- // 定义用户模型
- const userSchema = new mongoose.Schema({
- username:{type:String, unique:true}, //唯一,防止命名重复
- password:{type:String, set(val) //val是需要加密的值
- {
- return require('bcrypt').hashSync(val,10) //10加密强度
- }
- }, //加密
- email:{type:String,unique:true},
-
- });
-
- // 添加新的属性---如果后续有新的属性加入没有可以省略
- //userSchema.add({
- // avatar_url: {type:String}
- //});
-
- //创建用户模型(集合)
- const User = mongoose.model('User', userSchema);
-
- ...关闭连接
-
- module.exports = {db,User}
至此数据库的配置连接就完成了
- npm install express --save
-
- npm i body-parser
-
- npm i cors
- const express = require('express')
- const router = require('./router');
- const bodyParser = require('body-parser')
- const cors = require('cors');
- const app = express()
-
- app.use(cors()) //跨域处理
- app.use(bodyParser.json()) //解析请求体
-
- // 使用路由文件
- app.use('/',router);
-
-
- app.listen(3000, () => {
- console.log('server running ...');
- })
邮件发送我以及分离出来了,可以借鉴我的这篇博客.与这篇博客是对接上的.
Vue+NodeJS实现邮件发送
http://t.csdn.cn/OWAgl
唯一要修改的地方就是,一个邮箱注册一个账号,你也可以自定义多少个
- //发送邮件
- router.get('/getemail', async (req, res) => {
- try {
- const { mail } = req.query;
-
- // 验证邮箱是否存在
- const email = await User.findOne({ email: mail });
- if (email) {
- return res.status(422).json({ message: '邮箱存在账号' });
- }
-
- // 随机验证码
-
- // 将code存入缓存
-
- //发送邮件
-
- } catch (error) {
-
- });
安装jsonwebtoken
npm i jsonwebtoken
一些数据密钥可以存储在一个.env文件中,可以参考这篇博客
Vue+NodeJS上传图片到腾讯云Cos
http://t.csdn.cn/cwN0N
- const express = require('express');
- const router = express.Router();
- const bcrypt = require('bcrypt');
- const jwt = require('jsonwebtoken');
- const {User,db}=require('./database');
-
- const SECRET=*******... //密钥,鉴权登录的时候来解析token
-
- //注册
-
-
- //登录
-
-
- //鉴权登录
-
-
- //邮件发送
-
- module.exports = router
注册前,我们先发送邮件得到验证码,将验证码缓存.再请求注册接口的时候,先验证验证码的准确性.
如果错误或者过期了,不进行注册.
-
- //用户注册
- router.post('/register',async(req,res)=>{
-
- // 获取缓存---邮箱验证码
- let cachedValue = myCache.get("code");
-
- //解构出来前端传过来的信息
- let {code,username,password,email}=req.query.info
-
- if(!cachedValue){
- return res.status(422).send({ message: '验证码过期' });
- }
- if(code!=cachedValue){
- return res.status(422).send({ message: '验证码输入错误' });
- }
- try {
- // 验证成功后,插入数据
- const user = await User.create({
- username: username,
- password: password,
- email: email,
- });
-
- myCache.del("code");
-
- //将用户信息发送给前端
- res.send(user);
-
- } catch (error) {
- console.error(error);
- res.status(422).send({
- message: '该用户已经存在'
- });
- }
-
- })
这里我们是将用户的唯一_id生成token发给前端,前端将token存储到Cookie中,下次登录的时候将token发送给鉴权接口,读取token得到用户的_id.通过_id去查询数据库,得到用户的信息,实现登录.我们给Cookie设置时效性,在一定时间内,快速登录.
- //用户登录
- router.post('/login', async (req, res) => {
- try {
-
- let { username, password } = req.query.info;
- // 查询用户是否存在
- const user = await User.findOne({ username });
- // 用户名不存在
- if (!user) {
- return res.status(422).json({ message: '用户名不存在' });
- }
- // 校验密码
- //true或false
- const isPasswordValid = bcrypt.compareSync(password, user.password);
-
- if (!isPasswordValid) {
- return res.status(422).json({ message: '密码无效' });
- }
-
- // 生成 JWT
- const token = jwt.sign({ id: String(user._id) }, SECRET); //密钥
-
- res.json({ //发送json数据
- user,
- token
- });
- } catch (error) {
- console.log(error)
- }
-
- })
由于官方的规范,前端传过来的token格式是

所以将authorization切成两部分,取后面
- //用户鉴权
- router.get('/profile',async(req,res)=>{
- try {
-
- const rawToken = String(req.headers.authorization).split(' ').pop();
-
- const { id } = jwt.verify(rawToken, SECRET);
-
-
- const user = await User.findById(id);
- if (!user) {
- return res.status(404).json({ message: '用户不存在' });
- }
-
- res.json({user}); // 使用 .json() 方法发送 JSON 数据
- } catch (error) {
- console.log(error)
- }
- })
至此我们的后端服务搭建完成
这里我们将axios进行封装,多个请求时方便书写
- import axios from "axios";
-
- const http = axios.create({
- baseURL: 'http://127.0.0.1:3000', // 注意这里的双斜杠
- timeout: 5000
- });
-
- // 请求拦截器
- http.interceptors.request.use(config => {
- return config;
- }, error => {
- return Promise.reject(error);
- });
-
- // 响应拦截器
- http.interceptors.response.use(response => {
- return response.data;
- }, error => {
- return Promise.reject(error);
- });
-
- export default http;
- import http from './http';
-
- //注册
- export async function registerAPI(info) {
-
- const response = await http({
- url: 'register',
- method:'post',
- params: {info}
- });
-
- return response; // 返回响应数据
-
- }
-
- //登录
- export async function loginAPI(info) {
-
- const response = await http({
- url: 'login',
- method:'post',
- params: {info}
- });
- return response; // 返回响应数据
-
- }
-
- //鉴权登录
- export async function getTokenAPI(config) {
-
- const response = await http({
- url: 'profile',
- method:'get',
- // 自定义请求头
- headers:{authorization:config.authorization}
- });
-
- return response; // 返回响应数据
-
- }
-
- //邮箱
我们先进行输入内容的去空格处理和为空的判断.
如果没有输入邮箱,不能发送邮件.
如果用户名,密码,邮箱,验证码有一项为空,不能注册.
...
- import {registerAPI} from './api'
-
- ...
-
- // 注册
- function signUp(){
- //去除空格
- ...
- //判断是否为空
- ...
-
- let registerInfo={
- username:username.value,
- password:password.value,
- email:email.value,
- code:code.value
- }
-
- registerAPI(registerInfo)
- .then(response => {
- console.log('Register successful:', response);
- })
- .catch(error => {
- // 处理错误
- console.log(error)
- });
-
- }
在登录成功后,将token存储到Cookie中,设置存储时间.
- import {loginAPI} from './api'
- import useCookie from './Cookie';
- const { setCookie } = useCookie();
-
- ...
-
-
- //登录
- function SignIn(){
- //去除空格
- username.value=username.value.replace(/\s/g, '')
- password.value=password.value.replace(/\s/g, '')
-
- //判断是否为空
- if(username.value==''||password.value==''){
- console.log('格式不正确')
- }else{
- const loginInfo={username:username.value,password:password.value}
- loginAPI(loginInfo)
- .then(response => {
- setCookie('token', response.token, 0.5); // 存储30分钟
- console.log(response)
- })
- .catch(error => {
- // 处理错误
- console.log(error)
- });
- }
-
- }

-
- export default function useCookie() {
-
- //获取Cookie
- const getCookie = (name) => {
- const value = `; ${document.cookie}`;
- const parts = value.split(`; ${name}=`);
-
- if (parts.length === 2) {
- return parts.pop().split(';').shift();
- }
- };
-
- //设置Cookie
- const setCookie = (name, value, hours) => {
- const date = new Date();
- date.setTime(date.getTime() + (hours * 60 * 60 * 1000));
- const expires = `expires=${date.toUTCString()}`;
-
- document.cookie = `${name}=${value}; ${expires}; path=/`;
- };
-
- //销毁Cookie
- const deleteCookie=(name)=>{
- // 将Cookie的过期时间设置为过去的日期
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
- }
-
-
- return {
- getCookie,
- setCookie,
- deleteCookie
- };
- }
- import {getTokenAPI} from './api'
- import useCookie from './Cookie';
- const { getCookie } = useCookie();
-
- ...
-
- onMounted( async()=>{
- // 读取Token
- const Token = getCookie('token');
- const config = {
- authorization: `Bearer ${Token}`, // 设置Bearer Token
- };
-
- // 发送需要Token的请求
- await getTokenAPI(config)
- .then(response => {
- // 处理响应数据
- console.log(response)
- })
- .catch(error => {
- // 处理错误
- console.log(error)
- });
- })
至此前端页面完成.
如果有不对或者优化的地方欢迎指正,如果使用时出现了问题,可以留言评论,大家一起解决!
这里的token设置存在bug,没有真正的销毁token.有时间改正,有熟悉的大佬可以评论留言,给出好的解决办法.感谢!