当前位置: 首页 > 知识库问答 >
问题:

前端 - vue3+node.js如何开发系统中不同的账号有页面权限?

小牛23237
2025-01-10

给账号设置页面权限

共有1个答案

汝臻
2025-01-10

思路

1.数据库设计:

  • 设计用户、角色和权限相关的表结构。

2.后端实现:

  • 使用 Node.js 和 Express 实现用户认证和权限管理。

3.前端实现:

  • 使用 Vue3 和 Pinia 实现状态管理和路由权限控制。

4.组件权限控制:

  • 使用自定义指令或方法在组件层面控制权限。

目录结构

project-root/
├── backend/
│   ├── controllers/
│   │   └── authController.js
│   ├── models/
│   │   ├── user.js
│   │   ├── role.js
│   │   └── permission.js
│   ├── routes/
│   │   └── authRoutes.js
│   ├── config/
│   │   └── db.js
│   ├── middleware/
│   │   └── authMiddleware.js
│   └── server.js
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   └── UserButton.vue
│   │   ├── views/
│   │   │   ├── Home.vue
│   │   │   ├── Login.vue
│   │   │   └── Denied.vue
│   │   ├── store/
│   │   │   └── userPermissions.js
│   │   ├── router/
│   │   │   └── index.js
│   │   ├── App.vue
│   │   └── main.js
└── package.json

1. 数据库设计
创建用户、角色和权限相关的表:

-- 用户表,存储用户的基本信息
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,  -- 用户ID,主键,自增
  username VARCHAR(255) NOT NULL,     -- 用户名,非空
  password VARCHAR(255) NOT NULL,     -- 密码,非空
  role_id INT,                        -- 角色ID,外键
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  -- 创建时间,默认当前时间
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  -- 更新时间,默认当前时间,更新时自动更新
);

-- 角色表,存储不同的角色信息
CREATE TABLE roles (
  id INT AUTO_INCREMENT PRIMARY KEY,  -- 角色ID,主键,自增
  name VARCHAR(255) NOT NULL,         -- 角色名称,非空
  description VARCHAR(255),           -- 角色描述
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  -- 创建时间,默认当前时间
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  -- 更新时间,默认当前时间,更新时自动更新
);

-- 权限表,存储具体的权限信息
CREATE TABLE permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,  -- 权限ID,主键,自增
  name VARCHAR(255) NOT NULL,         -- 权限名称,非空
  description VARCHAR(255),           -- 权限描述
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  -- 创建时间,默认当前时间
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  -- 更新时间,默认当前时间,更新时自动更新
);

-- 角色权限关联表,存储角色与权限的对应关系
CREATE TABLE role_permissions (
  role_id INT,                        -- 角色ID,外键
  permission_id INT,                  -- 权限ID,外键
  PRIMARY KEY (role_id, permission_id),  -- 联合主键
  FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,  -- 角色ID外键,级联删除
  FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE  -- 权限ID外键,级联删除
);

-- 用户权限关联表,存储用户与权限的直接对应关系(可选)
CREATE TABLE user_permissions (
  user_id INT,                        -- 用户ID,外键
  permission_id INT,                  -- 权限ID,外键
  PRIMARY KEY (user_id, permission_id),  -- 联合主键
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,  -- 用户ID外键,级联删除
  FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE  -- 权限ID外键,级联删除
);

2. 后端实现
用户认证和权限管理:

  • 在 controllers/authController.js 中实现登录和权限检查逻辑。
  • 在 routes/authRoutes.js 中定义相关路由。
  • 在 middleware/authMiddleware.js 中实现权限中间件。

authController.js

const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { getUserByUsername, createUser } = require('../models/user');
const { getRoleById } = require('../models/role');
const { getPermissionsByRoleId } = require('../models/permission');

const login = async (req, res) => {
  const { username, password } = req.body;
  const user = await getUserByUsername(username);

  if (!user) {
    return res.status(401).json({ message: 'Invalid username or password' });
  }

  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) {
    return res.status(401).json({ message: 'Invalid username or password' });
  }

  const role = await getRoleById(user.role_id);
  const permissions = await getPermissionsByRoleId(user.role_id);

  const token = jwt.sign({ id: user.id, role: role.name, permissions: permissions.map(p => p.name) }, 'your_jwt_secret', { expiresIn: '1h' });

  res.json({ token });
};

const register = async (req, res) => {
  const { username, password, role_id } = req.body;
  const hashedPassword = await bcrypt.hash(password, 10);
  const userId = await createUser(username, hashedPassword, role_id);

  res.status(201).json({ id: userId });
};

module.exports = { login, register };

user.js

const pool = require('../config/db');

const getUserByUsername = async (username) => {
  const [rows] = await pool.query('SELECT * FROM users WHERE username = ?', [username]);
  return rows[0];
};

const createUser = async (username, password, role_id) => {
  const [result] = await pool.query('INSERT INTO users (username, password, role_id) VALUES (?, ?, ?)', [username, password, role_id]);
  return result.insertId;
};

module.exports = { getUserByUsername, createUser };

role.js

const pool = require('../config/db');

const getRoleById = async (id) => {
  const [rows] = await pool.query('SELECT * FROM roles WHERE id = ?', [id]);
  return rows[0];
};

module.exports = { getRoleById };

permission.js

const pool = require('../config/db');

const getPermissionsByRoleId = async (role_id) => {
  const [rows] = await pool.query('SELECT p.* FROM permissions p JOIN role_permissions rp ON p.id = rp.permission_id WHERE rp.role_id = ?', [role_id]);
  return rows;
};

module.exports = { getPermissionsByRoleId };

authRoutes.js

const express = require('express');
const { login, register } = require('../controllers/authController');

const router = express.Router();

router.post('/login', login);
router.post('/register', register);

module.exports = router;

db.js

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'your_database_name',
});

module.exports = pool;

authMiddleware.js

const jwt = require('jsonwebtoken');

const authenticate = (req, res, next) => {
  const token = req.header('Authorization').replace('Bearer ', '');
  if (!token) {
    return res.status(401).json({ message: 'Access denied' });
  }

  try {
    const decoded = jwt.verify(token, 'your_jwt_secret');
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ message: 'Invalid token' });
  }
};

const authorize = (requiredPermissions) => {
  return (req, res, next) => {
    const userPermissions = req.user.permissions;
    const hasPermission = requiredPermissions.every(permission => userPermissions.includes(permission));

    if (!hasPermission) {
      return res.status(403).json({ message: 'Forbidden' });
    }

    next();
  };
};

module.exports = { authenticate, authorize };

server.js:

const express = require('express');
const bodyParser = require('body-parser');
const authRoutes = require('./routes/authRoutes');

const app = express();
app.use(bodyParser.json());

app.use('/api/auth', authRoutes);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

3.前端实现
状态管理和路由权限控制:

  • 在 store/userPermissions.js 中存储用户角色和权限信息。
  • 在 router/index.js 中使用路由守卫检查权限。

UserButton.vue

<template>
  <div>
    <el-button v-if="hasPermission('sys:user:add')" type="success" plain>添加用户</el-button>
  </div>
</template>

<script>
import { userPermissionsStore } from '@/store/userPermissions';
import { storeToRefs } from 'pinia';

export default {
  setup() {
    const store = userPermissionsStore();
    const { userPermissions } = storeToRefs(store);

    const hasPermission = (permission) => {
      return userPermissions.value.includes(permission);
    };

    return { hasPermission };
  },
};
</script>

Home.vue

<template>
  <div>
    <h1>Home Page</h1>
    <UserButton />
  </div>
</template>

<script>
import UserButton from '@/components/UserButton.vue';

export default {
  components: {
    UserButton,
  },
};
</script>

Login.vue

<template>
  <div>
    <h1>Login Page</h1>
    <form @submit.prevent="login">
      <input v-model="username" placeholder="Username" />
      <input v-model="password" type="password" placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
import { userPermissionsStore } from '@/store/userPermissions';
import { useRouter } from 'vue-router';

export default {
  data() {
    return {
      username: '',
      password: '',
    };
  },
  setup() {
    const store = userPermissionsStore();
    const router = useRouter();

    const login = async () => {
      // 模拟登录请求
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: this.username, password: this.password }),
      });
      const data = await response.json();
      if (response.ok) {
        store.setUserPermissions(data);
        router.push('/home');
      } else {
        alert(data.message);
      }
    };

    return { login };
  },
};
</script>

Denied.vue

<template>
  <div>
    <h1>Access Denied</h1>
    <p>You do not have permission to view this page.</p>
  </div>
</template>

userPermissions.js

import { defineStore } from 'pinia';
import { ref } from 'vue';

export const userPermissionsStore = defineStore('userPermissions', () => {
  const roles = ref('');
  const userPermissions = ref([]);
  const isLogin = ref(false);

  const setUserPermissions = (params) => {
    userPermissions.value = params.permissions;
    roles.value = params.role;
    isLogin.value = true;
  };

  const logout = () => {
    userPermissions.value = [];
    roles.value = '';
    isLogin.value = false;
  };

  return { isLogin, userPermissions, roles, setUserPermissions, logout };
});

index.js

import { createRouter, createWebHashHistory } from 'vue-router';
import { userPermissionsStore } from '@/store/userPermissions';
import { storeToRefs } from 'pinia';

const routes = [
  {
    path: '/home',
    name: 'home',
    component: () => import('../views/Home.vue'),
    meta: { requireAuth: true, roles: ['admin', 'guest'] },
  },
  { path: '/login', name: 'login', component: () => import('../views/Login.vue') },
  { path: '/denied', name: 'denied', component: () => import('../views/Denied.vue') },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

router.beforeEach((to, from, next) => {
  const store = userPermissionsStore();
  const { isLogin, roles } = storeToRefs(store);

  if (to.meta.requireAuth) {
    if (isLogin.value) {
      if (to.meta.roles.includes(roles.value)) {
        next();
      } else {
        next('/denied');
      }
    } else {
      next('/login');
    }
  } else {
    next();
  }
});

export default router;

App.vue

<template>
  <router-view />
</template>

main.js:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(createPinia());
app.use(router);
app.mount('#app');

package.json

{
  "name": "vue3-nodejs-auth",
  "version": "1.0.0",
  "description": "A project with Vue3 and Node.js for user authentication and authorization",
  "main": "backend/server.js",
  "scripts": {
    "start": "node backend/server.js",
    "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
    "dev:backend": "nodemon backend/server.js",
    "dev:frontend": "cd frontend && npm run serve"
  },
  "dependencies": {
    "bcrypt": "^5.0.1",
    "body-parser": "^1.19.0",
    "concurrently": "^6.2.1",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1",
    "mysql2": "^2.2.5",
    "pinia": "^2.0.11",
    "vue": "^3.2.31",
    "vue-router": "^4.0.12"
  },
  "devDependencies": {
    "nodemon": "^2.0.7"
  }
}
 类似资料:
  • vue3页面中如何使用wx-open-subscribe,我vue3移动端里面怎么调起微信公众号订阅功能

  • 我使用已经有一段时间了,我认为自己对Java很熟悉。但我刚刚发现了,并立即对它的actor模式感兴趣(据我所知)。 现在,假设我的JavaScript技能与我的Scala/Java技能相当,我想关注这两种系统的实用性。特别是在web服务方面。 我的理解是,Node在处理许多并发操作方面非常出色。我想,一个用于资产管理系统的好的节点web服务将在处理同时提交更改的多个用户方面表现出色(在大型、高流量

  • 注意: 使用 自有账号体系时,开发者必须拥有自己的账号体系,如果没有请直接使用 若琪账号体系,谢谢。 登录流程 1、流程 2、接口 参数说明: 字段 类型 必须? 说明 userId String 是 用户id token String 否 用户登录token 示例代码: Swift: // token 有些平台没有 RokidMobileSDK.account.thirdpartyLogin(u

  • 注意: 使用 自有账号体系时,开发者必须拥有自己的账号体系,如果没有请直接使用 若琪账号体系,谢谢。 登录流程 1、流程 2、接口 参数说明: 字段 类型 必须? 说明 userId String 是 SDK接入方用户Id token String 否 SDK接入方用户token 举个大栗子: Java: RokidMobileSDK.account.thirdpartyLogin(userId,

  • vue3中使用keep-alive中include属性来缓存router-view 在第一层子级下缓存是生效得 但是在第二级缓存就不生效了 最终想实现得是在全局layout实现个页面缓存(不仅只有两级children还会有更多)、通过组件得name值配置或者路由信息配置 请求大佬指教������

  • 温馨提示:项目开源,目前已停止维护 发发记账系统简介 发发流水记账是中国第一款专门为个体商家量身打造的免费进销存软件,发发流水账紧紧围绕个体商户的进货、销售、统计这条业务主线。 它广泛适用于it电脑硬软件、化妆品、日用百货、五金建材、数码电器、电子元器件、服装、食品、药品、物资等批发零售门店的进销存管理。改产品系统简单易用,小巧灵活方便,无需专门培训,无需专业知识,使您轻松记账,方便管理,最终让您

  • 面试半小时 1、自我介绍 2、遍历数组方法(for in,for of,forEach,map) 3、遍历对象方法,哪些方法能遍历继承属性,哪些方法不能 4、不同情况下this指向问题 5、箭头函数this指向 6、call、apply、bind 区别 7、js 执行环境 8、闭包是什么,他的作用及用途、使用环境 9、为什么使用闭包时变量不会被垃圾回收机制销毁 10、变量声明提升有哪几种情况 11

  • 如何将unity3D+移动端+Vue3结合开发? 想要做一个具有校园3D建模可以导航移动的手机软件,可是不知道用什么办法可以将3者结合起来 用了unity的WebGl打包成html后,放到Hbuilder中用打包工具打包成apk,拿到apk后。手机下载下来会这样,以为很好解决,没想到起步就毫无头绪了,求大佬破局