14.权限管理
1 用户登录和菜单权限
后端
1.生成jwt
com.atguigu.common.jwt.JwtHelper
public class JwtHelper {
private static long tokenExpiration = 30 * 60 * 1000;//有效时长30分钟
private static String tokenSignKey = "123456";//签名密钥,进行数据加密
/**
* 根据用户 id 和用户名称, 生成token的字符串
* @param userId
* @param username
* @return
*/
public static String createToken(Long userId, String username) {
String token = Jwts.builder()
.setSubject("AUTH-USER")//分类
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))//设置token的有效时长
.claim("userId", userId)//设置主体内容
.claim("username", username)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)//签名部分
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
/**
* 从生成的token字符串中获取用户id
* @param token
* @return
*/
public static Long getUserId(String token) {
try {
if (StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer) claims.get("userId");
return userId.longValue();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/***
* 从生成的token字符串中获取用户名称
* @param token
* @return
*/
public static String getUsername(String token) {
try {
if (StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("username");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
String token = JwtHelper.createToken(1L, "admin");
System.out.println(token);
String username = JwtHelper.getUsername(token);
Long userId = JwtHelper.getUserId(token);
System.out.println("username = " + username);
System.out.println("userId = " + userId);
}
}
2.登录相关接口
com.atguigu.auth.controller.IndexController
@Api(tags = "登录相关接口")
@RestController
@RequestMapping("/admin/system/index")
public class IndexController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
/**
* 登录
*
* @return
*/
@ApiOperation("登录")
@PostMapping("/login")
public Result login(@RequestBody LoginVo loginVo) {
/* //{"code":200,"data":{"token":"admin-token"}}
HashMap<String, Object> map = new HashMap<>();
map.put("token", "admin-token");
return Result.ok(map);*/
// 1.获取输入的用户名和密码
// 2.根据用户名查询数据库
LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(SysUser::getUsername, loginVo.getUsername());
SysUser sysUser = sysUserService.getOne(lambdaQueryWrapper);
// 3.用户信息是否存在
if (sysUser == null) {
throw new GuiguException(201, "用户不存在");
}
// 4.判断密码
if (loginVo.getPassword() != null) {
String pwdMD5 = MD5.encrypt(loginVo.getPassword());
if (!sysUser.getPassword().equals(pwdMD5)) {
throw new GuiguException(201, "用户名或密码有误");
}
}
// 5.判断用户是否被禁用
if (sysUser.getStatus().intValue() == 0) {
throw new GuiguException(201, "用户被禁用了");
}
// 6.使用jwt根据用户id和用户名称生成token字符串
String token = JwtHelper.createToken(sysUser.getId(), sysUser.getUsername());
// 7.返回
Map<String, Object> map = new HashMap<>();
map.put("token", token);
return Result.ok(map);
}
/**
* 获取用户信息
*
* @return
*/
@ApiOperation("获取用户信息")
@GetMapping("/info")
public Result info(HttpServletRequest request) {
// 1.从请求头获取用户信息(获取请求头token字符串)
String token = request.getHeader("authorization");
// 2.从token字符串获取用户id或者用户名称
Long userId = JwtHelper.getUserId(token);
// 3.根据用户id查询数据库,把用户信息获取出来
SysUser sysUser = sysUserService.getById(userId);
// 4.根据用户id获取用户可以操作的菜单列表
//查询数据库动态构建路由结构,进行显示
List<RouterVo> routers = sysMenuService.findUserMenuListByUserId(userId);
// 5.根据用户id获取用户可以操作的菜单按钮权限
List<String> buttons = sysMenuService.findUserPermsByUserId(userId);
// 6、返回相应的数据
Map<String, Object> map = new HashMap<>();
map.put("name", sysUser.getName());
map.put("avatar", "http://localhost:9528/static/img/man.7e19522d.png");
map.put("roles", "[admin]");
//返回用户可以操作的菜单
map.put("routers", routers);
//返回用户可以操作的按钮
map.put("buttons", buttons);
return Result.ok(map);
}
/**
* 退出登录
*
* @return
*/
@ApiOperation("退出登录")
@PostMapping("/logout")
public Result logout() {
return Result.ok();
}
}
涉及的service方法 com.atguigu.auth.service.impl.SysMenuServiceImpl
/**
* 根据 用户id 获取用户可以操作的菜单列表
*
* @param userId
* @return
*/
@Override
public List<RouterVo> findUserMenuListByUserId(Long userId) {
// 菜单列表
List<SysMenu> sysMenuList = null;
// 1.判断当前用户是否是超级管理员 userId = 1 是超级管理员
// 1.1 如果是超级管理员,查询所有用户列表
if (userId.longValue() == 1) {
//查询所有菜单列表
LambdaQueryWrapper<SysMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(SysMenu::getStatus, 1);//当前菜单得是启用状态
lambdaQueryWrapper.orderByAsc(SysMenu::getSortValue);//升序排列
sysMenuList = baseMapper.selectList(lambdaQueryWrapper);
} else {
// 1.2 通过不是超级管理员,根据userId查询key操作的菜单列表
// 多表关联查询:sys_role,sys_role_menu,sys_menu
sysMenuList = baseMapper.findMenuListByUserId(userId);
}
// 2.把查询出来的数据列表,构造成框架要求的路由结构
// 先构建树形结构
List<SysMenu> sysMenuTreeList = MenuHelper.buildTree(sysMenuList);
// 再构建框架要求的路由结构
List<RouterVo> routerList = this.buildRouter(sysMenuTreeList);
return routerList;
}
/**
* 构建框架要求的路由结构
*
* @param menus
* @return
*/
public List<RouterVo> buildRouter(List<SysMenu> menus) {
// 创建list集合,存值最终数据
List<RouterVo> routers = new ArrayList<>();
// menus
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(buildRouter(children));
}
}
routers.add(router);
}
return routers;
}
/**
* 获取路由地址
*
* @param menu 菜单信息
* @return 路由地址
*/
public String getRouterPath(SysMenu menu) {
String routerPath = "/" + menu.getPath();
if (menu.getParentId().intValue() != 0) {
routerPath = menu.getPath();
}
return routerPath;
}
/**
* 根据 用户id 获取用户可以操作的按钮列表
*
* @param userId
* @return
*/
@Override
public List<String> findUserPermsByUserId(Long userId) {
// 1、判断是否是管理员,如果是管理员,查询所有按钮列表
List<SysMenu> sysMenusList = null;
if (userId.longValue() == 1) {
// 查询所有菜单列表
LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysMenu::getStatus, 1);
sysMenusList = baseMapper.selectList(queryWrapper);
} else {
// 2、如果不是管理员,根据userId查询可以操作按钮列表
// 多表关联查询:sys_role、sys_role_menu、sys_menu
sysMenusList = baseMapper.findMenuListByUserId(userId);
}
// 3、从查询出来的数据里面,获取可以操作按钮值的List集合,返回
List<String> permsList = sysMenusList.stream()
.filter(item -> item.getType() == 2)
.map(item -> item.getPerms())
.collect(Collectors.toList());
return permsList;
}
涉及的mapper语句 com.atguigu.auth.mapper.SysMenuMapper
// 通过userId多表关联查询:sys_role、sys_role_menu、sys_menu
List<SysMenu> findMenuListByUserId(@Param("userId") Long userId);
mapper的sql语句
<resultMap id="sysMenuMap" type="com.atguigu.model.system.SysMenu" autoMapping="true"></resultMap>
<select id="findMenuListByUserId" 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>
前端
2 用户认证和授权
1.在common模块下建子模块spring-security
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-security</artifactId>
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>common-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
2.自定义加密器PasswordEncoder com.atguigu.security.custom.CustomMd5PasswordEncoder
@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
3.自定义用户对象UserDetails com.atguigu.security.custom.CustomUser
public class CustomUser extends User {
/**
* 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
*/
private SysUser sysUser;
public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
super(sysUser.getUsername(), sysUser.getPassword(), authorities);
this.sysUser = sysUser;
}
public SysUser getSysUser() {
return sysUser;
}
public void setSysUser(SysUser sysUser) {
this.sysUser = sysUser;
}
}
4.根据用户名获取用户对象(获取不到直接抛异常) com.atguigu.security.custom.UserDetailsService
public interface UserDetailsService {
/**
* 根据用户名获取用户对象(获取不到直接抛异常)
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
5.com.atguigu.auth.service.impl.UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService { //改为 org.springframework.security.core.userdetails.UserDetails;
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询
SysUser sysUser = sysUserService.getUserByUserName(username);
if(null == sysUser) {
throw new UsernameNotFoundException("用户名不存在!");
}
if(sysUser.getStatus().intValue() == 0) {
throw new RuntimeException("账号已停用");
}
// 根据 user_id 查询用户操作权限数据
List<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());
// 创建list集合,封装最终权限数据
List<SimpleGrantedAuthority> authList = new ArrayList<>();
// 遍历 authList
for (String perm : userPermsList) {
authList.add(new SimpleGrantedAuthority(perm.trim()));
}
return new CustomUser(sysUser, authList);
}
}
6.com.atguigu.auth.service.SysUserService
//根据用户名查询
SysUser getUserByUserName(String username);
com.atguigu.auth.service.impl.SysUserServiceImpl
/**
* 根据用户名查询
*
* @param username
* @return
*/
@Override
public SysUser getUserByUserName(String username) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getUsername, username);
SysUser sysUser = baseMapper.selectOne(queryWrapper);
return sysUser;
}
7.自定义用户认证接口 com.atguigu.security.filter.TokenLoginFilter
/**
* @author Yangmc email:yangmc@tayo.com
* @create 2024-01-26 2:29
* @description 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验
*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private RedisTemplate redisTemplate;
// 构造方法
public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate){
this.setAuthenticationManager(authenticationManager);
this.setPostOnly(false);
//指定登录接口及提交方式,可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));
this.redisTemplate = redisTemplate;
}
// 登录认证过程
// 获取输入的用户名和密码,调用方法认证
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
// 获取用户信息
LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);
//封装对象
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
//调用方法
return this.getAuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 认证成功调用的方法
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException {
// 获取当前用户
CustomUser customUser = (CustomUser) auth.getPrincipal();
// 生成token
String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());
// 获取当前用户的权限数据,放到 redis 中,key:username value:权限数据
redisTemplate.opsForValue().set(customUser.getUsername(), JSON.toJSONString(customUser.getAuthorities()));
// 返回
Map<String, Object> map = new HashMap<>();
map.put("token", token);
ResponseUtil.out(response, Result.ok(map));
}
// 认证失败调用的方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
if(e.getCause() instanceof RuntimeException) {
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
} else {
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
}
}
}
8.认证解析token com.atguigu.security.filter.TokenAuthenticationFilter
因为用户登录状态在token中存储在客户端,所以每次请求接口请求头携带token, 后台通过自定义token过滤器拦截解析token完成认证并填充用户信息实体
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
logger.info("uri:"+request.getRequestURI());
//如果是登录接口,直接放行
if("/admin/system/index/login".equals(request.getRequestURI())) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if(null != authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} else {
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// token置于header里
String token = request.getHeader("token");
logger.info("token:"+token);
if (!StringUtils.isEmpty(token)) {
String username = JwtHelper.getUsername(token);
logger.info("username:"+username);
if (!StringUtils.isEmpty(username)) {
//当前用户信息放到ThreadLocal里面
LoginUserInfoHelper.setUserId(JwtHelper.getUserId(token));
LoginUserInfoHelper.setUsername(username);
// 通过 username 从 redis 中获取权限数据
String authString = (String) redisTemplate.opsForValue().get(username);
// 把 redis 获取的权限数据 转为 集合类型 List<SimpleGrantedAuthority>
if (StringUtils.isEmpty(authString)){
List<Map> mapList = JSON.parseArray(authString, Map.class);
System.out.println("mapList = " + mapList);
List<SimpleGrantedAuthority> authList = new ArrayList<>();
for (Map map : mapList) {
authList.add(new SimpleGrantedAuthority((String) map.get("authority")));
}
return new UsernamePasswordAuthenticationToken(username, null, authList);
}
} else {
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
}
return null;
}
}
9.com.atguigu.security.custom.CustomUser
public class CustomUser extends User {
/**
* 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
*/
private SysUser sysUser;
public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
super(sysUser.getUsername(), sysUser.getPassword(), authorities);
this.sysUser = sysUser;
}
public SysUser getSysUser() {
return sysUser;
}
public void setSysUser(SysUser sysUser) {
this.sysUser = sysUser;
}
}
10.配置用户认证 com.atguigu.security.config.WebSecurityConfig
@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService; // 装载的是 org.springframework.security.core.userdetails.UserDetailsService;
@Autowired
private CustomMd5PasswordEncoder customMd5PasswordEncoder;
@Autowired
private RedisTemplate redisTemplate;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
http
//关闭csrf跨站请求伪造
.csrf().disable()
// 开启跨域以便前端调用接口
.cors().and()
.authorizeRequests()
// 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
// .antMatchers("/admin/system/index/login").permitAll()
// 这里意思是其它所有接口需要认证才能访问
.anyRequest().authenticated()
.and()
//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
.addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)
.addFilter(new TokenLoginFilter(authenticationManager(),redisTemplate));
//禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 指定UserDetailService和加密器
auth.userDetailsService(userDetailsService)
.passwordEncoder(customMd5PasswordEncoder);
}
/**
* 配置哪些请求不拦截
* 排除swagger相关请求
*
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/admin/modeler/**","/diagram-viewer/**","/editor-app/**","/*.html",
"/admin/processImage/**",
"/admin/wechat/authorize","/admin/wechat/userInfo","/admin/wechat/bindPhone",
"/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
}
}
11.在service-utils模块的com.atguigu.common.config.exception.GlobalExceptionHandler添加 Spring Security对应的异常
/**
* spring security异常
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result error(AccessDeniedException e) throws AccessDeniedException {
return Result.build(null, ResultCodeEnum.LOGIN_ERROR);
}
3.整合前端
1.store/modules/user.js
新增菜单及按钮处理
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
buttons: [], // 新增
menus: '' // 新增
}
}
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_BUTTONS: (state, buttons) => {
state.buttons = buttons
},
// 新增
SET_MENUS: (state, menus) => {
state.menus = menus
}
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.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 } = 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)
})
})
},
// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
}).catch(error => {
reject(error)
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
2.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
3.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+
4.src/permission.js
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
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', '/forget', '/404', '/401'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
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.动态添加路由
const 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
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
}
5.src/router
删除index.js中自定义的路由,注释掉的就是
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/**
* Note: 路由配置项
*
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
* roles: ['admin', 'common'] // 访问路由的角色权限
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
* meta : {
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
}
*/
// 公共路由
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
meta: { title: '平台登录' },
hidden: true
},
{
path: '/forget', // 忘记密码
component: () => import('@/views/login/forget'),
meta: { title: '忘记密码' },
hidden: true
},
{
path: '/404', // 找不到页面
component: () => import('@/views/error/404'),
meta: { title: '找不到页面' },
hidden: true
},
{
path: '/401', // 没有访问权限
component: () => import('@/views/error/401'),
meta: { title: '401' },
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard', affix: true }
}]
},
{
path: '/user',
component: Layout,
hidden: true,
redirect: 'noredirect',
children: [
{
path: 'profile',
component: () => import('@/views/system/user/profile/index'),
name: 'Profile',
meta: { title: '个人中心', icon: 'user' }
}
]
}
// { path: '*', redirect: '/404', hidden: true },
// {
// path: '/system',
// name: 'system',
// component: Layout,
// meta: { title: '系统管理', icon: 'system', noCache: false, link: null },
// hidden: false,
// redirect: 'user',
// alwaysShow: true,
// children: [
// {
// path: 'user',
// name: 'user',
// component: () => import('@/views/system/user/index'),
// meta: { title: '用户管理', icon: 'user', noCache: false, link: null }
// },
// {
// path: 'role',
// name: 'role',
// component: () => import('@/views/system/role/index'),
// meta: { title: '角色管理', icon: 'peoples', noCache: false, link: null }
// },
// {
// path: 'menu',
// name: 'menu',
// component: () => import('@/views/system/menu/index'),
// meta: { title: '菜单管理', icon: 'tree-table', noCache: false, link: null }
// }
// ]
// }
]
// 防止连续点击多次路由报错
const routerPush = Router.prototype.push
const routerReplace = Router.prototype.replace
// push
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(err => err)
}
// replace
Router.prototype.replace = function push(location) {
return routerReplace.call(this, location).catch(err => err)
}
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 // reset router
}
export default router
6.src/components
在scr/components目录下新建ParentView文件夹,添加index.vue
<template >
<router-view />
</template>
7.layout/components/SideBar/index.vue
computed: {
...mapGetters([
'sidebar'
]),
routes() {
//return this.$router.options.routes
return this.$router.options.routes.concat(global.antRouter)
},
8.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
}
9.main.js
//新增
import hasBtnPermission from '@/utils/btn-permission'
Vue.prototype.$hasBP = hasBtnPermission
10.按钮权限控制
$hasBP(‘bnt.sysRole.add’)控制按钮是否显示
如:角色管理添加按钮,让按钮隐藏
<el-button v-if="$hasBP('bnt.sysRole.add')" type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>