node.js 几个功能插件使用总结

157 阅读2分钟

总结了项目上用到的几个小功能:multer 文件上传、nodemailer 发送邮件、jwt-simple 登录验证、map API 获取位置信息。

文件上传

Multer

Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。

安装

npm i express multer

配置

multer(opts) 配置

const multer = require('multer');

const maxSize = 3 * 1024 * 1024; // 大小限制
const fileTypes = ['png', 'jpg', 'jpeg', 'webp', 'gif'].map(type => `image/${type}`); // 类型限制

const filedir = '/files/uploads'; // 文件保存路径

const configs = {
  limits: {
    fileSize: maxSize,
  },
  fileFilter: (req, file, cb) => {
    const {mimetype} = file;
    if (fileTypes.includes(mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Allowed only .png, .jpg, .jpeg, .webp and .gif'), false);
    }
  },
};
  • storage:存储文件位置配置
  • limits:上传大小限制
  • fileFilter:文件过滤器,控制哪些文件可以被接受
const upload = multer({
  storage: storage,
  ...configs,
}).single('file'); // 单个文件上传
  • .single(fieldname):接受一个以 fieldname 命名的文件。这个文件的信息保存在 req.file。
  • .array(fieldname[, maxCount]):接受一个以 fieldname 命名的文件数组。可以配置 maxCount 来限制上传的最大数量。这些文件的信息保存在 req.files。
  • .fields(fields):接受指定 fields 的混合文件。这些文件的信息保存在 req.files。fields 应该是一个对象数组,应该具有 name 和可选的 maxCount 属性。
  • .none():只接受文本域。
  • .any():接受一切上传的文件。文件数组将保存在 req.files。

storage 配置

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, filedir);
  },
  filename: (req, file, cb) => {
    const {name, _id} = req.user;
    const ext = path.extname(req.file.originalname);
    const filename = `${_id}_${name}${ext}`; // 保存文件名
    cb(null, filename);
  },
});
  • destination:文件存储文件夹,可以是 string 或 function,如果没有设置 destination,则使用操作系统默认的临时文件夹。
  • filename:用于确定文件夹中的文件名。

使用

app.post('/uploads', (req, res) => {
  upload(req, res, error => {
    if (error instanceof multer.MulterError) {
      // MulterError
    } else if (error) {
      // error
    }
    // ok
  })
});

发送邮件

发送邮件配置信息

const nodemailer = require('nodemailer');

const sendEmail = (to, token, redirect) => {
  const transporter = nodemailer.createTransport({
    host: 'smtp.qq.com', // 邮件发送服务器
    port: 465, // 端口
    secure: true, // 使用 TLS
    auth: {
      user: 'xxx@qq.com', // 发送者邮箱
      pass: 'xxx', // 授权码
    },
  });
  const url = redirect || LOGIN_REDIRECT;
  const mailOptions = {
    from: `from xxx@qq.com`, // sender address
    to, // list of receivers
    subject: 'subject', // Subject line
    text: `text`, // plaintext body
    html: `html`, // html body
  };
  // send mail with defined transport object
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return error;
    }
    return info;
  });
};

使用

sendEmail(req.body.email);

jwt 登录

创建 jwt

const jwt = require('jwt-simple');

const jwtDecode = token => jwt.decode(token, TOKEN_SECRET);
const jwtEncode = token => jwt.encode(token, TOKEN_SECRET);

const createJWT = (user, delay = 1) => {
  const payload = {
    sub: user._id,
    iat: moment().unix(),
    exp: moment().add(delay, 'days').unix(),
  };
  return jwtEncode(payload);
};

登录

密码加密

const crypto = require('crypto');

const sha512 = (str, options) => crypto.createHmac('sha512', options.salt.toString()).update(str).digest('hex');

const shaPwd = password => sha512(password, {salt: SALT});

创建 token

const login = (db, req, res) => {
  db.findOne({name: req.body.name}, '+password', (err, user) => {
    if (!user) {
      return res.status(400).send({message: '该邮箱尚未注册!'});
    }
    const psd = shaPwd(req.body.password);
    if (user.password !== psd) {
      return res.status(403).send({message: '密码错误!'});
    }
    return res.status(200).send({token: createJWT(user), message: '登录成功!', result: user});
  });
};

请求验证中间件

const ensureAuthenticated = (req, res, next) => {
  if (!req.header('Authorization')) {
    return res.status(401).send({message: '用户未登录,请登录后操作!'});
  }
  const token = req.header('Authorization').split(' ')[1];
  if (!token) {
    return res.status(401).send({message: '用户未登录,请登录后操作!'});
  }
  let payload = null;
  try {
    payload = jwtDecode(token);
  } catch (err) {
    return res.status(401).send({message: err.message});
  }
  if (payload.exp <= moment().unix()) {
    return res.status(401).send({message: '验证信息过期,请重新登录!'});
  }
  next();
};

获取客户端位置信息

使用百度地图 API 获取地理位置信息。

获取 ak

进入百度地图开放平台创建应用,应用类别选服务端,即可生成 ak 。

通过 ip 获取信息

const {request} = require('../utils/request');

const apiUrl = 'https://api.map.baidu.com/location/ip';
const apiKey = 'xxx';

const getLocation = async ip => {
  try {
    const result = await request({url: apiUrl, params: {ak: apiKey, ip, coor: 'bd09ll'}, responseType: 'json'});
    const {address_detail = {}, point = {}} = result.data?.content || {};
    const {province, city, adcode} = address_detail;
    const rectangle = point.x ? `${point.x},${point.y}` : undefined;
    return {
      province,
      city,
      adcode,
      rectangle,
    };
  } catch (error) {
    throw Error(error);
  }
};

使用

const ipInfo = await getLocation(req.ip);