【NodeJS】NDB/Handling error/Token

NDB使用

安装:

  • npm i ndb --global
  • package中定义:“debug”: “ndb server.js”

在这里插入图片描述

设置断点

解决错误

错误场景:
在这里插入图片描述

解决未定义路由

将无法被解析的请求放在所有路由定义后面,这样就可以自动运行到它。

//all方法可以接受所有请求的方法
app.all('*', (req, res, next) => {
  res.status(404).json({
    status: 'fial',
    msg: `Can't find ${req.originalUrl} on this server`,
  });
});

将其集成到一个文件

重构error

重构error对象,这样就使catch中的错误信息可以被统一传入,只要发生错误就进入这个函数

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;
	//推到栈顶
    Error.captureStackTrace(this, this.constructor);
  }
}
module.exports = AppError;
//结构:err, req, res, next
module.exports = (err, req, res, next) => {
  //   console.log(err.stack);
  err.statusCode = err.statuseCode || 500;
  err.status = err.status || 'error';

  res.status(err.statusCode).json({
    status: err.status,
    msg: err.message,
  });
};

传入错误信息

app.all('*', (req, res, next) => {
  next(new AppError(`Can't find ${req.originalUrl} on this server`, 404));
});

app.use(globalErrorHandler);

集成trycatch

module.exports = fn => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

这样可以不使用trycatch语法块,而是使用链式函数,将try的内容返回

exports.getAllTours = catchAsync(async (req, res, next) => {
  const features = new APIFeatures(Tour.find(), req.query)
  const tours = await features.query;

  // SEND RESPONSE
  res.status(200).json({
    status: 'success',
    results: tours.length,
    data: {
      tours
    }
  });
});//在后面自动接了catch

区分开发环境的错误与客户端的错误

  • Programming or other unknown error: don’t leak error details
  • 将可被处理的错误推到客户端,其加了 this.isOperational = true;
const sendErrorDev = (err, res) => {
  res.status(err.statusCode).json({
    status: err.status,
    error: err,
    message: err.message,
    stack: err.stack
  });
};

const sendErrorProd = (err, res) => {
  // Operational, trusted error: send message to client
  if (err.isOperational) {
    res.status(err.statusCode).json({
      status: err.status,
      message: err.message
    });

    // Programming or other unknown error: don't leak error details
  } else {
    // 1) Log error
    console.error('ERROR 💥', err);

    // 2) Send generic message
    res.status(500).json({
      status: 'error',
      message: 'Something went very wrong!'
    });
  }
};

module.exports = (err, req, res, next) => {
  // console.log(err.stack);

  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    sendErrorDev(err, res);
  } else if (process.env.NODE_ENV === 'production') {
  //避免重新分配err=。。。,因此将其进行深拷贝
    let error = { ...err };

    if (error.name === 'CastError') error = handleCastErrorDB(error);
    if (error.code === 11000) error = handleDuplicateFieldsDB(error);
    if (error.name === 'ValidationError')
      error = handleValidationErrorDB(error);
    if (error.name === 'JsonWebTokenError') error = handleJWTError();
    if (error.name === 'TokenExpiredError') error = handleJWTExpiredError();

    sendErrorProd(error, res);
  }
};

区分不同类型的错误

const AppError = require('./../utils/appError');

const handleCastErrorDB = err => {
  const message = `Invalid ${err.path}: ${err.value}.`;
  return new AppError(message, 400);
};

const handleDuplicateFieldsDB = err => {
  const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
  console.log(value);

  const message = `Duplicate field value: ${value}. Please use another value!`;
  return new AppError(message, 400);
};

const handleValidationErrorDB = err => {
  const errors = Object.values(err.errors).map(el => el.message);

  const message = `Invalid input data. ${errors.join('. ')}`;
  return new AppError(message, 400);
};

const handleJWTError = () =>
  new AppError('Invalid token. Please log in again!', 401);

const handleJWTExpiredError = () =>
  new AppError('Your token has expired! Please log in again.', 401);

将无法处理的各类错误放入监听器处理

由于错误类型过多,因此对进程添加监听器方便处理没有添加catch的错误

process.on('uncaughtException', err => {
  console.log('UNCAUGHT EXCEPTION! 💥 Shutting down...');
  console.log(err.name, err.message);
  process.exit(1);
});

process.on('unhandledRejection', err => {
  console.log('UNHANDLED REJECTION! 💥 Shutting down...');
  console.log(err.name, err.message);
  server.close(() => {
    process.exit(1);
  });
});

登陆验证

密码加密

使用 npm i bcryptjs
使用中间件进行加密,加密行为置于保存行为之前

//在获取数据和保存数据间执行的中间件
userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next;

  //传入要加密的数据,加密程度
  this.password = await bcrypt.hash(this.password, 12);
  this.passwordConfirm = undefined;
  next();
});

JWT-Token

官网:
jwt简介:跳转到博客
Token简介:点点获取知识

实现注册
  • npm i jsonwebtoken
    在环境变量中设置加密字符串,使用jwt的sign方法进行token生成
//传入要加密的内容,加密代码,过期时间
  const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES,
  });
  res.status(201).json({
    status: 'success',
    token,
    newuser: newUser,
  });
实现登录

使用一个实例方法,使其在全文档可用,由于其定义于整个文档,因此this指向document,但是由于password被设置为无法选中,因此无法使用this.指向

//创建一个实例方法,检查密码是否一致
userSchema.methods.correctPassword = async function (
  candidatePassword,
  userPassword
) {
  return await bcrypt.compare(candidatePassword, userPassword);
};

实现登录操作
调用 jwt.sign() 生成 JWT 字符串,三个参数分别是:用户信息对象。加密秘钥、配置对象

const signToken = (id) =>
  jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES,
  });

exports.login = catchAsync(async (req, res, next) => {
  const { email, password } = req.body;
  console.log(req.body);
  //检查是否存在email和password
  if (!email || !password) {
    //不加return这里会发送两次请求,错误报告和res
    return next(new AppError('Please provide your email and password!', 400));
  }
  //检查是否正确,此处password为不可被选中的,因此需要加一个加号
  const user = await User.findOne({ email: email }).select('+password');
  //对比密码
  if (!user || !(await user.correctPassword(password, user.password))) {
    return next(new AppError('Incorrect email or password', 401));
  }

  //发送token
  const token = signToken(user._id);
  res.status(200).json({
    status: 'success',
    token,
  });
});
实现每次请求的确认

首先在需要验证的路由前加入中间件进行验证
在请求头加入Authorization,一般结构:Bearer+token
在函数中获取Authorization: req.headers.authorization
将其与原始密匙进行解码:jwt.verify(token, process.env.JWT_SECRET), 此时可以获取到我们所加密的对象

接下来进行比对各种条件


exports.protect = catchAsync(async (req, res, next) => {
  // 获取token
  let token;
  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith('Bearer')
  ) {
    token = req.headers.authorization.split(' ')[1];
  }
  if (!token) {
    return next(new AppError('you are not logged in!', 401));
  }

  // 验证token
  const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
  console.log(decoded);

  //检查用户是否存在,防止在登陆后用户删除账户,此时再进行验证还可以登录的情况
  const freshUser = await User.findById(decoded.id);
  if (!freshUser) {
    return next(new AppError('The token is not exsist', 401));
  }

  // 检测用户是否更改密码,传入token的时间戳,对比其与数据库中更改账户的最新实践,如果小于数据库的时间戳,则说明密码被更改
  if (freshUser.changedPasswordAfter(decoded.iat)) {
    return next(new AppError('User recently changed password,log again!', 401));
  }
  //验证通过
  req.user = freshUser;
  next();
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值