web系统动态菜单及路由的方法如下:前端根据获取的用户角色信息,动态生成路由

1.后端
  1. 创建数据库表如下所示:包含用户表和角色表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for roles
-- ----------------------------
DROP TABLE IF EXISTS `roles`;
CREATE TABLE `roles`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
  `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编码',
  `description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  `created_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `code`(`code` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `role_id` int NOT NULL COMMENT '角色ID',
  `created_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;

  1. 后端核心接口如下:主要是一个获取用户信息接口,这个接口会返回roles字段,表示请求接口的用户的角色信息。
@ApiOperation("获取用户信息")
    @ApiImplicitParam(name = "Authorization", value = "Bearer token", required = true, paramType = "header")
    @GetMapping("/info")
    public Result<Map<String, Object>> getUserInfo(@RequestHeader("Authorization") String token) {
        try {
            String identifier = jwtUtil.getIdentifierFromToken(token);
            LoginType loginType = jwtUtil.getLoginTypeFromToken(token);
            
            User user;
            if (LoginType.EMAIL.equals(loginType)) {
                user = userService.getByEmail(identifier).get();
            } else {
                user = userService.getByPhone(identifier).get();
            }

            if (user != null) {
                Map<String, Object> userInfo = new HashMap<>();
                String roleCode = roleService.getRoleCode(user.getRoleId());
                
                userInfo.put("roles", new String[]{roleCode});
                userInfo.put("name", user.getUsername());
                userInfo.put("avatar", user.getAvatar());
                userInfo.put("userId", user.getId());
                userInfo.put("createTime", user.getCreatedTime());
                userInfo.put("email", user.getEmail());
                userInfo.put("phone", user.getPhone());

                return Result.success(userInfo);
            }
            return Result.error("用户不存在");
        } catch (Exception e) {
            log.error("获取用户信息失败", e);
            return Result.error("获取用户信息失败");
        }
    }
2.前端
  1. 在src/router/index.js中创建静态路由和动态路由:
import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);

/* Layout */
import Layout from "@/layout";


// 静态路由,所有角色都可以访问
export const constantRoutes = [
  {
    path: "/login",
    component: () => import("@/views/login/index"),
    hidden: true,
  },

  {
    path: "/404",
    component: () => import("@/views/404"),
    hidden: true,
  },

  {
    path: "/",
    component: Layout,
    redirect: "/dashboard",
    children: [
      {
        path: "dashboard",
        name: "Dashboard",
        component: () => import("@/views/dashboard/index"),
        meta: { title: "首页", icon: "dashboard" },
      },
    ],
  },

  {
    path: "/register",
    component: () => import("@/views/register/index"),
    hidden: true,
  },

  {
    path: "/profile",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "",
        component: () => import("@/views/profile/index"),
        name: "Profile",
        meta: { title: "更改密码" },
      },
    ],
  },
];

// asyncRoutes 动态路由
export const asyncRoutes = [
  {
    path: "/user",
    component: Layout,
    meta: {
      roles: ['ADMIN', 'SUPER_ADMIN']
    },
    children: [
      {
        path: "index",
        name: "User",
        component: () => import("@/views/user/index"),
        meta: { title: "用户管理", icon: "el-icon-user",roles: ['ADMIN', 'SUPER_ADMIN'] },
      },
    ],
  },

  {
    path: "/service",
    component: Layout,
    meta: {
      roles: ['SUPER_ADMIN']
    },
    children: [
      {
        path: "index",
        name: "Service",
        component: () => import("@/views/service/index"),
        meta: { title: "服务配置", icon: "el-icon-setting", roles: ['SUPER_ADMIN'] },
      },
    ],
  },

  {
    path: "/fingerprintMatch",
    component: Layout,
    meta: {
      roles: ['NORMAL', 'ADMIN', 'SUPER_ADMIN']
    },
    children: [
      {
        path: "index",
        name: "FingerprintMatch",
        component: () => import("@/views/FingerprintMatch/index"),
        meta: { title: "指纹比对", icon: "el-icon-camera", roles: ['NORMAL', 'ADMIN', 'SUPER_ADMIN'] },
      },
    ],
  },

  {
    path: "/fingerprintManage",
    component: Layout,
    meta: {
      roles: [ 'ADMIN', 'SUPER_ADMIN']
    },
    children: [
      {
        path: "index",
        name: "FingerprintManage",
        component: () => import("@/views/FingerprintManage/index"),
        meta: { title: "指纹管理", icon: "el-icon-document",roles: ['ADMIN', 'SUPER_ADMIN'] },
      },
    ],
  },

  {
    path: "/history",
    component: Layout,
    meta: {
      roles: [ 'ADMIN', 'SUPER_ADMIN', 'NORMAL']
    },
    children: [
      {
        path: "index",
        name: "History",
        component: () => import("@/views/history/index"),
        meta: { title: "比对记录", icon: "el-icon-time",roles: ['ADMIN', 'SUPER_ADMIN', 'NORMAL'] },
      },
      {
        path: "detail/:id",
        name: "HistoryDetail",
        component: () => import("@/views/history/detail"),
        meta: { title: "比对详情",roles: ['ADMIN', 'SUPER_ADMIN', 'NORMAL'] },
        hidden: true,
      },
    ],
  },
];

const createRouter = () =>
  new Router({
    mode: 'history', // 去掉url中的#
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRoutes,
  });

const router = createRouter();

// 重置路由函数
export function resetRouter() {
  const newRouter = createRouter();
  router.matcher = newRouter.matcher;
}

export default router;

  1. 在src\store\modules\permission.js中定义如下:
import { asyncRoutes, constantRoutes } from '@/router'

/**
 * 使用用户角色和route.meta.roles判断用户是否有路由权限
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

/**
 * 过滤路由表,将有权限的路由过滤出来
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

const state = {
  routes: [],   // 可访问路由
  addRoutes: [] // 动态添加的路由
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, data ) {
    return new Promise(resolve => {
      const { roles } = data;
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
} 
  1. 在src\store\modules\user.js中定义如下:
import { login, logout, getInfo, register, sendVerifyCode } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'

const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: '',
    userId: '',
    roles: []
  }
}

const state = getDefaultState()

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  SET_USER_ID: (state, id) => {
    state.userId = id
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles
  }
}

const actions = {
  // user login
  login({ commit }, userInfo) {
    const { email, phone, password, loginType } = userInfo
    return new Promise((resolve, reject) => {
      login({ email, phone, password, loginType }).then(response => {
        const { data } = response
        const { token} = data
        commit('SET_TOKEN', token)
        setToken(token) 
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response

        if (!data) {
          return reject('Verification failed, please Login again.')
        }

        const { name, avatar, userId, roles } = data

        const rolesArray = typeof roles === 'string' ? JSON.parse(roles) : roles

        if (!rolesArray || rolesArray.length <= 0) {
          reject('getInfo: roles must be a non-null array!')
        }

        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_USER_ID', userId)
        commit('SET_ROLES', rolesArray)

        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // user logout
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        removeToken()
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  // remove token
  resetToken({ commit }) {
    return new Promise(resolve => {
      removeToken()
      commit('RESET_STATE')
      resolve()
    })
  },

  // 发送验证码
  sendVerifyCode({ commit }, data) {
    return new Promise((resolve, reject) => {
      sendVerifyCode(data).then(response => {
        resolve(response)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // 注册
  register({ commit }, data) {
    return new Promise((resolve, reject) => {
      register(data).then(response => {
        resolve(response)
      }).catch(error => {
        reject(error)
      })
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}


  1. src\store\getters.js:
const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  roles: state => state.user.roles,
  addRouters: state => state.permission.addRoutes
}
export default getters

  1. src\store\index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import permission from './modules/permission'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,      // 管理应用级别的状态
    settings, // 管理应用设置
    user,     // 管理用户状态
    permission// 管理权限和路由
  },
  getters
})

export default store

  1. src\permission.js:
import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "@/utils/auth";
import getPageTitle from "@/utils/get-page-title";

NProgress.configure({ showSpinner: false });

const whiteList = ["/login", "/register"];

router.beforeEach(async (to, from, next) => {
  NProgress.start();

  document.title = getPageTitle(to.meta.title);

  const hasToken = getToken();

  if (hasToken) {
    if (to.path === "/login") {
      next({ path: "/" });
    } else {
      // 判断当前用户是否包含角色信息
      if (store.getters.roles.length === 0) {
        store.dispatch("user/getInfo").then((res) => {
            const roles = res.roles;
            store.dispatch("permission/generateRoutes", {roles}).then(() => {
              router.addRoutes(store.getters.addRouters);
              // hack方法 确保addRoutes已完成
              next({ ...to, replace: true });
            });
          })
          .catch((err) => {
            console.log(err);
          });
      } else {
        next(); 
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next();
    } else {
      next(`/login`);
      NProgress.done();
    }
  }
});

router.afterEach(() => {
  NProgress.done();
});

  1. src\layout\components\Sidebar\index.vue下:
routes() {
    return this.$store.state.permission.routes
}