手把手教你实现一个vue3+ts+nodeJS后台管理系统(十五)

207 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情

前言

本系统后端的用户、角色、菜单模块已经基本完成了。但用户登录后还应获得此用户的基本信息、角色信息、菜单信息,只要前端请求头带上了token,就能够根据此token获得此登录用户的id,相应的获取这些信息也并不困难。除此之外,若是想要实现类似个人中心的效果,还需要能够修改已登录用户的信息包括头像,接下来我们就着手来实现。

获取已登录用户的信息

在前面的文章中,我们提到express-jwt这个中间件可以解析token,经过它解析后的信息(为我们添加token时所设置的),通过req.user加相应字段名就可以访问到

image.png

那我们通过req.user.id就可以访问到已登录用户的id了,接下来凭此id能够去获取此用户的其它信息。除了用户信息例如用户名、邮箱等这些可以直接从表中获取到,还有角色通过表的联结也可以获取,但菜单(不包括按钮)的信息就需要接口能够根据多个角色(一个用户可能是多角色,每个角色可能会有权限的重叠)查询到菜单的树状结构。

那么该如何实现呢?前提有sequelize能够通过数组查询数据库,我们先从角色权限表查询到传来的用户角色id数组中所有角色id的权限id将其转化为数组,将权限id数组去重。再在权限表查询这些权限id的菜单信息按照type字段去除按钮将剩下的菜单再转化为树状结构即可。接下来我们着手编写此方法。

// 获取角色资源的方法
const getResource = async (role_id) => {
  // 所有按钮的父id集合(重复)
  let all_parent_ids = [];
  // 所有按钮的父id(去除重复)
  let parent_ids = [];
  // 返回的按钮集合 按钮项格式为{menu_id:xx,btns:[xx,xx]}
  const buttons = [];
  // 获取角色菜单表中此角色id的所有记录
  const roleResource = await RolesMenusModel.findAll({
    where: { role_id: role_id }
  });
  // 获得此角色id的拥有权限id
  let all_menu_ids = roleResource.map((resource) => {
    return resource.menu_id;
  });
  // 将权限id数组去重
  all_menu_ids = Array.from(new Set(all_menu_ids));
  // 从菜单表获取此角色id拥有权限详细信息
  const all_menus = await MenusModel.findAll({
    where: { menu_id: all_menu_ids },
    attributes: ['menu_id', 'parent_id', 'type', 'permission']
  });
  // 获取目录及菜单的id数组
  const menu__arr = all_menus.filter((menu) => menu.type === 'M' || menu.type === 'C');
  const menu_ids = menu__arr.map((menu) => menu.menu_id);
  // 将获取的按钮数组转化为对应的格式
  const btn_arr = all_menus.filter((menu) => menu.type === 'B');
  btn_arr.forEach((button) => {
    all_parent_ids.push(button.parent_id);
  });
  parent_ids = Array.from(new Set(all_parent_ids));
  parent_ids.forEach((item) => {
    buttons.push({ menu_id: item, btns: [] });
  });
  btn_arr.forEach((button) => {
    parent_ids.forEach((parent) => {
      if (button.parent_id === parent) {
        buttons.forEach((item) => {
          if (item.menu_id === parent) item.btns.push(button.permission);
        });
      }
    });
  });
  return {
    menu_ids,
    buttons
  };
};

通过这个方法,我们获取到了登录用户拥有角色的所有菜单id的数组和按钮数组,接下来再到菜单表查询出菜单信息即可。下面是完整的获取登录用户信息的接口(添加路由略)。

router_handler/userinfo.js

// 获取用户基本信息的处理函数
exports.getUserinfo = async (req, res) => {
  // 因为加入了expressJWT中间件解析token的原因,所以在请求头传递了token通过req.user.id就可访问到登录用户的id
  const user_id = req.user.id;
  // 查找已登录的用户详细信息
  const user_roles = await UsersModel.findOne({
    attributes: { exclude: ['password'] },
    include: [{ model: RolesModel, attributes: ['role_id', 'role_name', 'status'] }],
    where: {
      user_id: user_id
    }
  });
  // 若无用户信息提示错误
  if (!user_roles) {
    return res.send({
      code: 1,
      message: '帐号未分配角色',
      data: ''
    });
  }
  let role_ids = [];
  let role_names = [];
  // 获取该用户所拥有的角色
  user_roles.roles.forEach(function (item) {
    if (item.status) {
      role_ids.push(item.role_id);
      role_names.push(item.role_name);
      // result = await getResource(item.role_id);
      // menu_ids = result.menu_ids;
      // buttons = result.buttons;
    }
  });
  // 根据角色id数组获取权限
  const resource = await getResource(role_ids);
  // 根据菜单id数组获取菜单详细信息
  const menus = await MenusModel.getListTree({ menu_id: resource.menu_ids });
  return res.send({
    code: 0,
    message: '获取成功',
    data: {
      roles: role_names,
      user_id: user_id,
      name: user_roles.username,
      nickname: user_roles.nickname,
      email: user_roles.email,
      avatar: user_roles.user_pic,
      menus: menus,
      buttons: resource.buttons
    }
  });
};

修改已登录用户的信息

修改用户信息不涉及角色权限的修改,比较容易实现,在此不再多做赘述。我们重点来看修改头像。

首先,我们通过接口传递图片信息,但是正常的通过req.body获取入参的手段对图片是不管用的,我们需要一个额外的中间件formidable,让服务器得以解析获取图片。

formidable的文档详见formidable

安装依赖

npm install formidable@2.0.1

然后图片的话必须通过form-data的请求参数形式。然后通过formidable的生成对象解析图片存到我们的服务器路径即可,但解析出来的图片是以二进制的形式传输,所以解析出来的图片没有后缀没有文件的名字。所以我们对解析出来的图片重命名存储在服务器并暴露图片的静态资源目录以便我们可以访问此服务器静态资源目录,再将存储的路径储存在数据库即可访问该图片。

// 更新用户头像接口
exports.updateAvatar = (req, res) => {
  // 获取登录用户的id
  let user_id = req.user.id;
  let info = {};
  // 初始化处理文件对象
  let form = new formidable.IncomingForm();
  form.uploadDir = './public/avatar'; // 指定解析对象(图片)存放的目录
  form.keepExtensions = true; //保留后缀名
​
  form.parse(req, function (error, fields, files) {
    if (error) {
      info.code = 1;
      info.message = '上传头像失败';
      info.data = null;
      res.send(info);
    }
    // fields 除了图片外的信息
    // files 图片信息
​
    const generateFilename = (originalFilename, path) => {
      let names = originalFilename.split('.');
      path = path.replace('invalid-name', '');
      return `${path}${names[0]}_${req.user.id}.${names[1]}`;
    };
​
    // 通过fs更改文件名
    const newFilePath = generateFilename(files.file.originalFilename, files.file.filepath);
    fs.rename(files.file.filepath, newFilePath, (err) => {
      if (err) {
        console.log('重命名失败');
        console.log(err);
      } else {
        console.log(
          `已经保存为${generateFilename(files.file.newFilename, files.file.originalFilename, files.file.filepath)}`
        );
      }
    });
    const result = UsersModel.update(
      { user_pic: newFilePath },
      {
        where: {
          user_id: user_id
        }
      }
    );
    result.then(function (ret) {
      if (ret) {
        return res.send({
          code: 0,
          message: '重置头像成功',
          data: {
            srcUrl: newFilePath
          }
        });
      } else {
        return res.send({
          code: 1,
          message: ret,
          data: null
        });
      }
    });
  });
};

测试

1.获取登录用户信息

image.png

2.更新用户头像

image.png

image.png