权限管理项目
项目地址:https://gitee.com/zheng-linmou/permission-item/tree/master
1.角色分配菜单功能
1.1.获取树形结构的菜单
public class MenuHelper {
//构建树形结构 sysMenuList全部数据
public static List<SysMenu> bulidTree(List<SysMenu> sysMenuList) {
//创建集合封装最终数据
List<SysMenu> trees = new ArrayList<>();
//遍历所有菜单集合
for (SysMenu sysMenu : sysMenuList) {
// 找到递归入口,parentid=0
if (sysMenu.getParentId().longValue() == 0) {
trees.add(findChildren(sysMenu, sysMenuList));
}
}
return trees;
}
/**
* 深度优先搜索
*
* @param sysMenu
* @param treeNodes
* @return
*/
//从根节点进行递归查询,查询子节点
// 判断 id = parentid是否相同,如果相同是子节点,进行数据封装
private static SysMenu findChildren(SysMenu sysMenu, List<SysMenu> treeNodes) {
//数据初始化
sysMenu.setChildren(new ArrayList<SysMenu>());
//遍历递归查找
for (SysMenu it : treeNodes) {
//获取当前菜单id
//比对
if (Long.parseLong(sysMenu.getId()) == it.getParentId()) {
if (sysMenu.getChildren() == null) {
// 进行初始化
sysMenu.setChildren(new ArrayList<>());
}
// 找他下面的菜单
sysMenu.getChildren().add(findChildren(it, treeNodes));
}
}
return sysMenu;
}
}
1.2.根据userId查询所有的role
/**
* 根据用户获取角色数据
*
* @param userId
* @return
*/
@Override
public Map<String, Object> getRolesByUserId(Long userId) {
//获取所有角色
List<SysRole> roles = list(null);
//根据用户id查询
List<String> userRoleIds = sysUserRoleService.getRoleIdsByUserId(userId);
// 如果这个人没有权限的话全部显示为空就可以了
// 不能为null,必须为空集合
if(userRoleIds == null) {
userRoleIds = new ArrayList<>();
}
//创建返回的Map
Map<String, Object> returnMap = new HashMap<>();
returnMap.put("allRoles", roles);
returnMap.put("userRoleIds", userRoleIds);
return returnMap;
}
1.3.给用户分配角色
/**
* 根据用户分配角色
*
* @param assginRoleVo
*/
@Override
public void doAssign(AssginRoleVo assginRoleVo) {
// 根据用户id删除原来分配的角色
LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUserRole::getUserId, assginRoleVo.getUserId());
sysUserRoleService.remove(queryWrapper);
// 获取所有的角色id
// 从前端获取用户的选择
List<String> roleIdList = assginRoleVo.getRoleIdList();
for (String roleId : roleIdList) {
if (!StringUtils.isEmpty(roleId)) {
SysUserRole sysUserRole = new SysUserRole();
sysUserRole.setUserId(assginRoleVo.getUserId());
sysUserRole.setRoleId(roleId);
//保存
sysUserRoleService.save(sysUserRole);
}
}
}
1.4.给角色分配权限
/**
* 给角色分配权限
*
* @param assginMenuVo
*/
@Override
public void doAssign(AssginMenuVo assginMenuVo) {
// 先删除原来的
LambdaQueryWrapper<SysRoleMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(SysRoleMenu::getRoleId, assginMenuVo.getRoleId());
sysRoleMenuService.remove(lambdaQueryWrapper);
// 又重新加入进去
// 遍历所有已选择的权限id
for (String menuId : assginMenuVo.getMenuIdList()) {
if (menuId != null) {
//创建SysRoleMenu对象
SysRoleMenu sysRoleMenu = new SysRoleMenu();
sysRoleMenu.setMenuId(menuId);
sysRoleMenu.setRoleId(assginMenuVo.getRoleId());
//添加新权限
sysRoleMenuService.save(sysRoleMenu);
}
}
}
2.springsecurity
这个就可以使用原来的我单独学的springsecurity
来进行改造
3.动态路由
这个根据一下的数据进行修改就可以了总结(cv大法)
1、后端
你需要先构造出三级菜单的类型
在构造我们前端的动态路由
1.2、获取用户的基本信息
// 用户信息接口
@GetMapping("info")
public R info(HttpServletRequest request) {
Map<String, Object> map = sysUserService.info(request);
return R.ok().put("data", map);
}
实现方法
/**
* 用户信息接口
*
* @param request
* @return
*/
@Override
public Map<String, Object> info(HttpServletRequest request) {
// 得到token字符串
String token = request.getHeader("token");
// 用户名
String username = JwtHelper.getUsername(token);
LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(SysUser::getUsername, username);
// 得到用户id
SysUser sysUser = getOne(lambdaQueryWrapper);
Map<String, Object> result = new HashMap<>();
result.put("name", username);
result.put("avatar", "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
result.put("roles", "admin");
// 菜单的权限数据
List<RouterVo> routerVoList = sysMenuService.getUerMenuList(sysUser.getId());
result.put("routers", routerVoList);
// 按钮权限数据
// 根据用户id获取用户按钮权限
List<String> permsList = sysMenuService.findUserPermsList(sysUser.getId());
result.put("buttons", permsList);
return result;
}
菜单的权限数据(分析)
- 我们还要根据不用的用户的来判断超级管理员
/**
* 根据用户id查询权限菜单
*
* @param userId
* @return
*/
@Override
public List<RouterVo> getUerMenuList(String userId) {
// admin是超级管理员, 操作所有内容
List<SysMenu> sysMenuList = null;
// 如果id是1的话就是超级管理员 就查询全部就可以了
if ("1".equals(userId)) {
LambdaQueryWrapper<SysMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(SysMenu::getStatus, 1);
lambdaQueryWrapper.orderByDesc(SysMenu::getSortValue);
// 表示是超级管理员
sysMenuList = list(lambdaQueryWrapper);
} else {
// 不是超级管理员
// 根据用户id查询权限数据
sysMenuList = baseMapper.getUerMenuList(userId);
}
// 构建是树形的结构
List<SysMenu> sysMenusTree = MenuHelper.bulidTree(sysMenuList);
// 需要变成前端路由里面的index.js里面的文件的样式
List<RouterVo> routerVoList = RouterHelper.buildRouters(sysMenusTree);
return routerVoList;
}
普通管理员的菜单查询的
sql
语句
<select id="getUerMenuList" resultMap="sysMenuMap">
select
distinct
(m.id ,m.parent_id,m.name,m.type,m.path,m.component,m.perms,m.icon,m.sort_value,m.status,m.create_time,m.update_time,m.is_deleted)
from sys_menu m
inner join sys_role_menu rm on rm.menu_id = m.id
inner join sys_user_role ur on ur.role_id = rm.role_id
where
ur.user_id = #{userId}
and m.status = 1
and rm.is_deleted = 0
and ur.is_deleted = 0
and m.is_deleted = 0
</select>
1.3.工具类RouterHelper
根据生成的三级菜单来成功生成前端的动态路由
/**
*根据菜单数据构建路由的工具类
*/
public class RouterHelper {
/**
* 根据菜单构建路由
* @param menus
* @return
*/
public static List<RouterVo> buildRouters(List<SysMenu> menus) {
List<RouterVo> routers = new LinkedList<RouterVo>();
for (SysMenu menu : menus) {
RouterVo router = new RouterVo();
router.setHidden(false);
router.setAlwaysShow(false);
router.setPath(getRouterPath(menu));
router.setComponent(menu.getComponent());
router.setMeta(new MetaVo(menu.getName(), menu.getIcon()));
List<SysMenu> children = menu.getChildren();
//如果当前是菜单,需将按钮对应的路由加载出来,如:“角色授权”按钮对应的路由在“系统管理”下面
if(menu.getType().intValue() == 1) {
List<SysMenu> hiddenMenuList = children.stream().filter(item -> !StringUtils.isEmpty(item.getComponent())).collect(Collectors.toList());
for (SysMenu hiddenMenu : hiddenMenuList) {
RouterVo hiddenRouter = new RouterVo();
hiddenRouter.setHidden(true);
hiddenRouter.setAlwaysShow(false);
hiddenRouter.setPath(getRouterPath(hiddenMenu));
hiddenRouter.setComponent(hiddenMenu.getComponent());
hiddenRouter.setMeta(new MetaVo(hiddenMenu.getName(), hiddenMenu.getIcon()));
routers.add(hiddenRouter);
}
} else {
if (!CollectionUtils.isEmpty(children)) {
if(children.size() > 0) {
router.setAlwaysShow(true);
}
router.setChildren(buildRouters(children));
}
}
routers.add(router);
}
return routers;
}
/**
* 获取路由地址
*
* @param menu 菜单信息
* @return 路由地址
*/
public static String getRouterPath(SysMenu menu) {
String routerPath = "/" + menu.getPath();
if(menu.getParentId().intValue() != 0) {
routerPath = menu.getPath();
}
return routerPath;
}
}
2、store/modules/user.js
新增菜单及按钮处理
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
buttons: [], // 新增
menus: '' //新增
}
}
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_BUTTONS: (state, buttons) => {
state.buttons = buttons
},
// 新增
SET_MENUS: (state, menus) => {
state.menus = menus
}
}
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { name, avatar } = data
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit("SET_BUTTONS", data.buttons)
commit("SET_MENUS", data.routers)
resolve(data)
}).catch(error => {
reject(error)
})
})
}
3、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,
//新增
buttons: state => state.user.buttons,
menus: state => state.user.menus
}
export default getters
4、src/router
先在router这个目录下新建两个js文件,开发环境和生产环境导入组件的方式略有不同
_import_production.js
// 生产环境导入组件
module.exports = file => () => import('@/views/' + file + '.vue')
_import_development.js
// 开发环境导入组件
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
5、src/permission.js
整体替换该文件
import router from './router'
import store from './store'
import { getToken } from '@/utils/auth'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // 水平进度条提示: 在跳转路由时使用
import 'nprogress/nprogress.css' // 水平进度条样式
import getPageTitle from '@/utils/get-page-title' // 获取应用头部标题的函数
import Layout from '@/layout'
import ParentView from '@/components/ParentView'
const _import = require('./router/_import_'+process.env.NODE_ENV) // 获取组件的方法
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
// get user info
await store.dispatch('user/getInfo')// 请求获取用户信息
if (store.getters.menus.length < 1) {
global.antRouter = []
next()
}
const menus = filterAsyncRouter(store.getters.menus)// 1.过滤路由
console.log(menus)
router.addRoutes(menus) // 2.动态添加路由
let lastRou = [{ path: '*', redirect: '/404', hidden: true }]
router.addRoutes(lastRou)
global.antRouter = menus // 3.将路由数据传递给全局变量,做侧边栏菜单渲染工作
next({
...to,
replace: true
})
//next()
} catch (error) {
// remove token and go to login page to re-login
console.log(error)
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else { /* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => { // finish progress bar
NProgress.done()
}) // // 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') {
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else {
try {
route.component = _import(route.component)// 导入组件
} catch (error) {
debugger
console.log(error)
route.component = _import('dashboard/index')// 导入组件
}
}
}
if (route.children && route.children.length > 0) {
route.children = filterAsyncRouter(route.children)
} else {
delete route.children
}
return true
})
return accessedRouters
}
6、src/router
删除index.js中自定义的路由,以下注释内容即为要删除的内容
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: 'Dashboard', icon: 'dashboard' }
}]
}
//添加我们的路由
// {
// path: '/system',
// component: Layout,
// meta: {
// title: '系统管理',
// icon: 'el-icon-s-tools'
// },
// alwaysShow: true,
// children: [
// {
// path: 'sysRole',
// component: () => import('@/views/system/sysRole/list'),
// meta: {
// title: '角色管理',
// icon: 'el-icon-s-help'
// },
// },
// {
// path: 'sysUser',
// component: () => import('@/views/system/sysUser/list'),
// meta: {
// title: '用户管理',
// icon: 'el-icon-s-help'
// },
// },
// {
// name: 'sysMenu',
// path: 'sysMenu',
// component: () => import('@/views/system/sysMenu/list'),
// meta: {
// title: '菜单管理',
// icon: 'el-icon-s-unfold'
// },
// },
// {
// path: 'assignAuth',
// component: () => import('@/views/system/sysRole/assignAuth'),
// meta: {
// activeMenu: '/system/sysRole',
// title: '角色授权'
// },
// hidden: true,
// }
// ]
// },
// 404 page must be placed at the end !!!
// { path: '*', redirect: '/404', hidden: true }
]
7、src/components
在scr/components目录下新建ParentView文件夹,添加index.vue
<template >
<router-view />
</template>
8、layout/components/SideBar/index.vue
computed: {
...mapGetters([
'sidebar'
]),
routes() {
//return this.$router.options.routes
return this.$router.options.routes.concat(global.antRouter)
},
9、utils/btn-permission.js
在uitls目录添加btn-permission.js文件
import store from '@/store'
/**
* 判断当前用户是否有此按钮权限
* 按钮权限字符串 permission
*/
export default function hasBtnPermission(permission) {
// 得到当前用户的所有按钮权限
const myBtns = store.getters.buttons
// 如果指定的功能权限在myBtns中, 返回true ==> 这个按钮就会显示, 否则隐藏
return myBtns.indexOf(permission) !== -1
}
10、main.js
//新增
import hasBtnPermission from '@/utils/btn-permission'
Vue.prototype.$hasBP = hasBtnPermission
11、按钮权限控制
$hasBP(‘bnt.sysRole.add’)控制按钮是否显示
如:角色管理添加按钮,我们没让按钮隐藏,而是让按钮不可操作
<el-button type="success" icon="el-icon-plus" size="mini" @click="add" :disabled="$hasBP('bnt.sysRole.add') === false">添 加</el-button>