eyb:Redis集成菜单到权限组菜单更新功能(二)


目录:

(1)Redis集成菜单功能

(2)根据请求url判断角色

(3)判断用户角色

(4)职位管理功能实现

(5)全局异常处理 

(6)职称管理功能实现

(7)权限组角色功能实现

(8)权限组菜单查询功能实现

(9)权限组菜单更新功能实现


 

(1)Redis集成菜单功能

类似于后台管理系统的菜单,基本上不会有太大的变化,但是它会频繁的被读取和渲染这时候呢完全可以把菜单放到redis里面去,加快它加载的速度

首先在yeb-server项目中加入依赖:

在配置文件中加redis的相关信息application.yml:

server:
  port: 8081

spring:
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: "jdbc:mysql://localhost:3306/yeb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
    username: root
    password: 123456
    hikari:
      #连接池名
      pool-name: DateHikarCP
      #最小空闲连接
      minimum-idle: 5
      #空闲连接存货最大时间,默认600000(10分钟)
      idle-timeout: 180000
      #最大连接数,默认 10
      maximum-pool-size: 10
      #从连接池返回 的连接的自动提交
      auto-commit: true
      #连接最大存活时间,0表示永久存货,默认1800000(30分钟)
      max-lifetime: 1800000
      #连接超时时间,默认30000(30秒)
      connection-timeout: 30000
      #测试连接是否可用的查询语句
      connection-test-query: SELECT 1

      redis:
        #超时时间
        timeout: 10000ms
        #服务器地址
        host: 192.168.67.128
        #服务器端口
        port: 6379
        database: 0 # 选择哪个库,默认0库
        #密码
        password: root
        lettuce:
          pool:
            max-active: 1024 # 最大连接数,默认 8
            max-wait: 10000ms # 最大连接阻塞等待时间,单位毫秒,默认 -1
            max-idle: 200 # 最大空闲连接,默认 8
            #最小空闲连接
            min-idle: 5

mybatis-plus:
  #配置Mapper映射文件
  mapper-locations: classpath*:/mapper/*Mapper.xml
  #配置mybatis数据返回类型别名
  type-aliases-package: com.xxxx.server.pojo
  configuration:
    # 自动驼峰命名
    map-underscore-to-camel-case: false
## Mybatis SQL打印(方法接口所在的包,不是Mapper.xml所在的包)
logging:
  level:
    com.xxxx.server.mapper: debug

jwt:
  # JWT存储的请求头
  tokenHeader: Authorization
  # JWT 加解密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(60*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer

 RedisConfig:模板配置,序列化

package com.xxxx.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        //为string类型的key序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //为string类型的value设置序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //为hash类型的key序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //为hash类型的value设置序列化
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        //放连接工厂
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        return redisTemplate;
    }
}

修改Controller

里面菜单接口的实现类:

MenuServiceImpl:

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.mapper.MenuMapper;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.pojo.Menu;
import com.xxxx.server.service.IMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //根据用户id查询菜单列表
    @Override
    public List<Menu> getMenusByAdminId() {
        Integer adminId=((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();

        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        // 从redis中获取数据 ,第一次获取不会获取到任何数据
        List<Menu> menus =(List<Menu>) valueOperations.get("menu_" + adminId);

        //如果为空,去数据库中获取
        if (CollectionUtils.isEmpty(menus)){
            menus= menuMapper.getMenusByAdminId(adminId);
            //把获取到的redis放到redis里面
            valueOperations.set("menu_"+adminId,menus);
        }
        return menus;
    }
}

打开redis6319P.conf的Redis服务器

 

 根据adminId查询菜单列表,第一次查询,Redis中没有,去数据库中查询,查询陈宫后,会放到Redis中,第二次查询,直接在Redis中拿到Menus,不会再去查询数据库了,提高了查询效率

 启动项目:

去接口文档发送请求:

第一次查询:查询数据库t_admin中的表 

 

在次查询:查询的并不是Menu表,在每次查询之前都会有登录操作,这是查询登录的

 

 redis也没任何变化

还有一点需要注意,这里只是查询,如果有时候修改了菜单,虽然菜单很少被修改,如果我们修改了菜单,不管更新菜单,添加菜单,做了任何的修改,都需要把Redis里面的数据清空,然后下次查询的时候呢再次放置进去,如果不清空的话,查询的只是之前的数据,但是你应经修改了,只要你对菜单做任何的修改,都要把Redis里面的数据进行删除

 

 (2)根据请求url判断角色

做到这里,还没有设计到权限的概念,只是在表查询的时候关联了权限表,没有做其他的一些处理,但是这只是正常登录,但是用户如果在地址栏里面输入它本身权限没有的一个url,我们也要对它进行一个控制,这时候就涉及到权限的一些相关问题

 

我们是在登录的情况下,对用户的角色进行相应的判断,通过登录之后呢,根据用户去拿到自己对应的角色,可以根据登录判断自己的角色

还可以根据请求的url判断角色,我们访问url,url就在菜单表t_menu里面,我们会根据url找到对应的菜单id,再根据中间表,判断它需要那些角色,在判断拥有这些url的权限,进行比较就可以你这个用户是否真的有访问这个菜单的权限了 

根据url获取角色 

 在pojo Menu类加:

在service: IMenuService接口创建方法 

 

package com.xxxx.server.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxxx.server.pojo.Menu;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface IMenuService extends IService<Menu> {

    //根据用户id查询菜单列表
    List<Menu> getMenusByAdminId();

    //根据角色获取菜单列表
    List<Menu> getMenusWithRole();
}

 实现类实现:

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.mapper.MenuMapper;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.pojo.Menu;
import com.xxxx.server.service.IMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //根据用户id查询菜单列表
    @Override
    public List<Menu> getMenusByAdminId() {
        Integer adminId=((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();

        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        // 从redis中获取数据 ,第一次获取不会获取到任何数据
        List<Menu> menus =(List<Menu>) valueOperations.get("menu_" + adminId);

        //如果为空,去数据库中获取
        if (CollectionUtils.isEmpty(menus)){
            menus= menuMapper.getMenusByAdminId(adminId);
            //把获取到的redis放到redis里面
            valueOperations.set("menu_"+adminId,menus);
        }
        return menus;
    }


    //根据角色获取菜单列表
    @Override
    public List<Menu> getMenusWithRole() {
        return menuMapper.getMenusWithRole();
    }
}

在menuMapper 创建return里面的方法

package com.xxxx.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxxx.server.pojo.Menu;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface MenuMapper extends BaseMapper<Menu> {

    //根据用户id查询,菜单列表
    List<Menu> getMenusByAdminId(Integer id);

    //根据角色获取菜单列表
    List<Menu> getMenusWithRole();
}

 去写sql语句:

 先去Navcat:中编写测试查询:

SELECT
m.*,
r.id AS rid,
r.`name` AS rname,
r.nameZh AS rnameZh
FROM
t_menu m,
t_menu_role mr,
t_role r
WHERE
m.id=mr.mid
AND
r.id=mr.rid
ORDER BY m.id

 

 

MenuMapper.xml:添加

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxxx.server.mapper.MenuMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.xxxx.server.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, url, path, component, name, iconCls, keepAlive, requireAuth, parentId, enabled
    </sql>

    <resultMap id="Menus" type="com.xxxx.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.xxxx.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>
    
    <resultMap id="MenusWithRole" type="com.xxxx.server.pojo.Menu" extends="BaseResultMap">
        <collection property="roles" ofType="com.xxxx.server.pojo.Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
            <result column="rnameZh" property="naemZh"/>
        </collection>
    </resultMap>


    <!--根据用户id查询菜单列表-->
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT
            m1.*,
            m2.id AS id2,
            m2.url AS url2,
            m2.path AS path2,
            m2.component AS component2,
            m2.`name` AS `name2`,
            m2.iconCls AS iconCls2,
            m2.keepAlive AS keepAlive2,
            m2.requireAuth AS requireAuth2,
            m2.parentId AS parentId2,
            m2.enabled AS enabled2
        FROM
            t_menu m1,
            t_menu m2,
            t_admin_role ar,
            t_menu_role mr
        WHERE
             m1.id=m2.parentId
        AND
             m2.id=mr.mid
        AND
             mr.rid=ar.rid
        AND
             ar.adminId=#{id}
        AND
             m2.enabled=TRUE
        ORDER BY
             m2.id
    </select>

    <!--根据角色获取菜单列表-->
    <select id="getMenusWithRole" resultMap="MenusWithRole">
        SELECT
            m.*,
            r.id AS rid,
            r.`name` AS rname,
            r.nameZh AS rnameZh
        FROM
            t_menu m,
            t_menu_role mr,
            t_role r
        WHERE
            m.id=mr.mid
        AND
            r.id=mr.rid
        ORDER BY
            m.id
    </select>

</mapper>

 这个方法就写完了,我们就不是去写一个接口暴露出去了,让前端去调了,而是放到过滤器里面的,准备一个过滤器

CustomFilter:

package com.xxxx.server.config.security.component;

import com.xxxx.server.pojo.Menu;
import com.xxxx.server.pojo.Role;
import com.xxxx.server.service.IMenuService;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * 权限控制
 * 根据请求url分析出请求所需角色
 *
 * @author zhanglishen
 */
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {

    //注入IMenuService
    @Autowired
    private IMenuService menuService;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取请求的url
        String requestUrl = ((FilterInvocation)o).getRequestUrl();
        //System.out.println("requestUrl = " + requestUrl);
        //调用方法,获取菜单
        List<Menu> menus = menuService.getMenusWithRole();
        //System.out.println("menus = " + menus);
        for (Menu menu : menus) {
            //判断请求的url与菜单角色是否匹配
            if (antPathMatcher.match(menu.getUrl(),requestUrl)){
                //System.out.println("menu = " + menu.getUrl());
                String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                //System.out.println("str = " + str);
                return SecurityConfig.createList(str);
            }
        }
        //没匹配的url默认为登录即可访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

(3)判断用户角色

判断你登录的用户有那些角色

根据你请求的url获取的角色,和你登录之后的角色进行一个比较,如果有一致的,说明你的用户呢可以访问这些菜单资源

在pojo Admin类中修改:

 

package com.xxxx.server.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author zhanglishen
 * @since 2022-07-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_admin")
@ApiModel(value="Admin对象", description="")
public class Admin implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "姓名")
    private String name;

    @ApiModelProperty(value = "手机号码")
    private String phone;

    @ApiModelProperty(value = "住宅电话")
    private String telephone;

    @ApiModelProperty(value = "联系地址")
    private String address;

    @ApiModelProperty(value = "是否启用")
    private Boolean enabled;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "用户头像")
    private String userFace;

    @ApiModelProperty(value = "备注")
    private String remark;

    @ApiModelProperty(value = "角色")
    @TableField(exist = false)
    private List<Role> roles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //把角色里面对应的名字转换SimpleGrantedAuthority返回
        List<SimpleGrantedAuthority> authorities=roles
                .stream().map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

然后,去写判断用户角色的方法

IAdminService接口:

 

package com.xxxx.server.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.pojo.Menu;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.pojo.Role;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface IAdminService extends IService<Admin> {

    //登录方法 登录之后返回token
    RespBean login(String username, String password, String code, HttpServletRequest request);

    //根据用户名获取用户
    Admin getAdminByUserName(String username);

    //根据用户id查询角色列表
    List<Role> getRoles(Integer id);
}

AdminServiceImpl:实现类

注入RoleMapper、实现方法

 

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.config.security.component.JwtTokenUtil;
import com.xxxx.server.mapper.AdminMapper;
import com.xxxx.server.mapper.RoleMapper;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.pojo.Role;
import com.xxxx.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {

    //用到查数据库需要注入
    @Autowired
    private AdminMapper adminMapper;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    //注入工具类
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private RoleMapper roleMapper;

    //通过注解去拿头部信息
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    //登录之后返回token
    @Override
    public RespBean login(String username, String password, String code, HttpServletRequest request) {
        //获取验证码
        String  captcha =(String) request.getSession().getAttribute("captcha");
        //判断 如果输入验证码为空或者输入的验证码不正确
        if (StringUtils.isEmpty(code)||!captcha.equalsIgnoreCase(code)){
            return RespBean.error("验证码输入错误,请重新输入!");
        }

        //登录
        //获取到userDetaiils
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (null==userDetails||!passwordEncoder.matches(password,userDetails.getPassword())){
            return RespBean.error("用户名或密码不正确");
        }
        if (!userDetails.isEnabled()){
            return RespBean.error("账号被禁用!请联系管理员");
        }


        //更新security登录用户对象,把userDetails对象放到security全文中
        UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);


        //生成token
        //调用工具类的方法拿到token
        String token = jwtTokenUtil.generateToken(userDetails);
        Map<String,String> tokenMap=new HashMap<>();
        tokenMap.put("token",token);
        tokenMap.put("tokenHead",tokenHead);

        return RespBean.success("登录成功",tokenMap);//把token返回给前端
    }


    //根据用户名获取
    @Override
    public Admin getAdminByUserName(String username) {
        //使用MyBatis-plus查询
        return adminMapper.selectOne(new QueryWrapper<Admin>().eq("username",username).eq("enabled",true));

    }



    //根据用户id查询角色列表
    @Override
    public List<Role> getRoles(Integer adminId) {
        return roleMapper.getRoles(adminId);
    }


}

RoleMapper接口:创建这个方法

package com.xxxx.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxxx.server.pojo.Role;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface RoleMapper extends BaseMapper<Role> {

    根据用户id查询角色列表
    List<Role> getRoles(Integer adminId);
}

写sql语句:

SELECT
r.id,
r.`name`,
r.nameZh

FROM
t_role AS r
LEFT JOIN t_admin_role AS ar ON r.id=ar.rid
WHERE ar.adminId=1

 

 RoleMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxxx.server.mapper.RoleMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.xxxx.server.pojo.Role">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="nameZh" property="nameZh" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, nameZh
    </sql>

    <!--根据用户id查询角色列表-->
    <select id="getRoles" resultType="com.xxxx.server.pojo.Role">
        SELECT
            r.id,
            r.`name`,
            r.nameZh

        FROM
            t_role AS r
        LEFT JOIN t_admin_role AS ar ON r.id=ar.rid
        WHERE
            ar.adminId=#{adminId}
    </select>
</mapper>

去登录方法,把写的方法加上去,加上去之后呢,每次登录完之后呢,就可以获取到角色列表,

LoginController:

 

package com.xxxx.server.controller;

import com.xxxx.server.pojo.Admin;
import com.xxxx.server.pojo.AdminLoginParam;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.service.IAdminService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;

@Api(tags = "LoginController") //Swagger注解文档
@RestController
public class LoginController {

    //注入service 接口
    @Autowired
    private IAdminService adminService;

    @ApiOperation(value = "登录之后返回token")
    @PostMapping("/login")
    public RespBean login(@RequestBody  AdminLoginParam adminLoginParam, HttpServletRequest request){
        //调用接口IAdminService接口类中的login方法
        return adminService.login(adminLoginParam.getUsername(),adminLoginParam.getPassword(),adminLoginParam.getCode(),request);
    }


    @ApiOperation(value = "获取当前登录用户的信息")
    @GetMapping("/admin/info")
    public Admin getAdminInfo(Principal principal){
        if (null == principal) {
            return null;
        }
        //用户名
        String username = principal.getName();
        //根据用户名获取完整的对象
        Admin admin=adminService.getAdminByUserName(username);
        admin.setPassword(null);
        //登录后返回角色列表
        admin.setRoles(adminService.getRoles(admin.getId()));
        return admin;
    }


    @ApiOperation(value = "退出登录")
    @PostMapping("/logout")
    public RespBean logout(){
        return RespBean.success("注销成功");
    }
}

SecurityConfig:

package com.xxxx.server.config.security;

import com.xxxx.server.config.security.component.JwtAuthenticationTokenFilter;
import com.xxxx.server.config.security.component.RestAuthorizationEntryPoint;
import com.xxxx.server.config.security.component.RestfulAccessDeniedHandler;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Lazy
    @Autowired
    private IAdminService adminService;


    //注入以下刚创建的两个类
    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    //security走我们重写的UserDetails
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //2.把自己重写的UserDetail放进来,需要passwordEncorder下面注入进来
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    //6.设置放行一些路径,不走拦截连
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/login",
                "/logout",
                "/CSS/**",
                "/js/**",
                "/index.html",
                "favicon.ico",
                "/doc.html",
                "/webjars/**",//放行Swagger接口文档
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/captcha"

        );
    }

    //3. springsecurity的配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //使用JWT不需要csrf
        http.csrf()
                .disable()
                //基于token,不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //允许访问登录
                .authorizeRequests()

                //.antMatchers("/login","/logout")
                //.permitAll()
                //除了上面的请求,所有请求需要被拦截
                .anyRequest()
                .authenticated()
                .and()
                //禁用缓存
                .headers()
                .cacheControl();

        //4.添加jwt授权拦截器
        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        //5.添加自定义未授权、未定义结果的返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthorizationEntryPoint);



    }


    // 1.覆盖重写UserDetailsService 里面的LoadUserByUserName
    @Override
    @Bean
    public UserDetailsService userDetailsService(){
        return username -> {
            //调用IAdminService接口中的获取用户信息方法
            Admin admin = adminService.getAdminByUserName(username);
            if (null!=admin){
                //返回角色列表
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }
            throw new UsernameNotFoundException("用户名和密码不正确");
        };
    }


    //暴露PasswordEncoder对象
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //暴露出来对象
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }
}

添加拦截过滤器:CustomUrlDecisionManager

package com.xxxx.server.config.security.component;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/*
* 权限控制
*   判断用户角色
* */
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            //访问当前url所需的角色
            String needRole = configAttribute.getAttribute();
            //判断角色是否为登录即可访问的角色,ROLE_LOGIN
            if ("ROLE_LOGIN".equals(needRole)){
                //判断登录
                if (authentication instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("CustomUrlDecisionManager : 尚未登录,请登录!");
                }else {
                    return;
                }
            }
            //判断角色是否为url所需角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("CustomUrlDecisionManager : 权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

 在SecurityConfig进行配置:注入

 

package com.xxxx.server.config.security;

import com.xxxx.server.config.security.component.*;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Lazy
    @Autowired
    private IAdminService adminService;


    //注入以下刚创建的两个类
    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    //注入 创建的过滤器
    @Autowired
    private CustomFilter customFilter;
    @Autowired
    private CustomUrlDecisionManager customUrlDecisionManager;

    //security走我们重写的UserDetails
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //2.把自己重写的UserDetail放进来,需要passwordEncorder下面注入进来
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    //6.设置放行一些路径,不走拦截连
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/login",
                "/logout",
                "/CSS/**",
                "/js/**",
                "/index.html",
                "favicon.ico",
                "/doc.html",
                "/webjars/**",//放行Swagger接口文档
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/captcha"

        );
    }

    //3. springsecurity的配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //使用JWT不需要csrf
        http.csrf()
                .disable()
                //基于token,不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //允许访问登录
                .authorizeRequests()

                //.antMatchers("/login","/logout")
                //.permitAll()
                //除了上面的请求,所有请求需要被拦截
                .anyRequest()
                .authenticated()
                //动态权限配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {

                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilter);
                        return object;
                    }
                })

                .and()
                //禁用缓存
                .headers()
                .cacheControl();

        //4.添加jwt授权拦截器
        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        //5.添加自定义未授权、未定义结果的返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthorizationEntryPoint);



    }


    // 1.覆盖重写UserDetailsService 里面的LoadUserByUserName
    @Override
    @Bean
    public UserDetailsService userDetailsService(){
        return username -> {
            //调用IAdminService接口中的获取用户信息方法
            Admin admin = adminService.getAdminByUserName(username);
            if (null!=admin){
                //返回角色列表
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }
            throw new UsernameNotFoundException("用户名和密码不正确");
        };
    }


    //暴露PasswordEncoder对象
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //暴露出来对象
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }
}

测试接口:

helloController:

package com.xxxx.server.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

//测试
@RestController
public class HelloController {

    @GetMapping("hello")
    public String hello(){
        return "hello";
    }

    @GetMapping("/employee/basic/hello")
    public String hello2(){
        return "/employee/basic/hello";
    }

    @GetMapping("/employee/advanced/hello")
    public String hello3(){
        return "/employee/advanced/hello";
    }
}

点击hello2:

 

点击hello3:

 

 (4)职位管理功能实现

职位管理需要的表:t_position

 

 修改创建时间自定义格式:

 PositionController:添加职位的基本操作

package com.xxxx.server.controller;


import com.xxxx.server.pojo.Position;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.service.IPositionService;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@RestController
@RequestMapping("/system/basic/pos")
public class PositionController {

    @Autowired
    private IPositionService positionService;

    @ApiOperation(value = "获取所有职位信息")
    @GetMapping("/")
    public List<Position> getAllPositions(){
        return positionService.list();
    }

    @ApiOperation(value = "添加职位信息")
    @PostMapping("/")
    public RespBean addPosition(@RequestBody Position position){
        position.setCreateDate(LocalDateTime.now());
        if (positionService.save(position)){
            return RespBean.success("添加成功");
        }
        return RespBean.error("添加失败");
    }

    @ApiOperation(value = "更新职位信息")
    @PutMapping("/")
    public RespBean updatePosition(@RequestBody Position position){
        if (positionService.updateById(position)){
            return RespBean.success("更新成功");
        }
        return RespBean.error("更新失败");
    }

    @ApiOperation(value = "删除职位信息")
    @DeleteMapping("/{id}")
    public RespBean deletePosition(@PathVariable Integer id){
        if (positionService.removeById(id)){
            return RespBean.success("删除成功");
        }
        return RespBean.error("删除失败");
    }

    @ApiOperation(value = "批量删除职位信息")
    @DeleteMapping("/")
    public RespBean deletePositionByIds(Integer[] ids){
        if (positionService.removeByIds(Arrays.asList(ids))){
            return RespBean.success("删除成功");
        }
        return RespBean.error("删除失败");
    }
}

添加:

 

数据库中的表: 

 

 在 添加几个信息:

获取所有职位信息:

下面是添加的 

 

 

 更新:

 

 

 

 删除职位信息:

删除id=6

 

批量删除职位信息 

 

可以看到使用MyBatis-plus之后,非常简单,它主要使用在单表上,在菜单的时候,自己写的sql语句,因为涉及到多表,其实MyBatis-plus,也可以使用多表,但是非常负责没有直接写sql语句方便,单表的话处理起来就非常快了

(5)全局异常处理 

职位管理基本实现完了,但是还是有问题的,我们删除的都是我们刚开始创建的职位信息,比如说删除职位信息,我们删除存在的职位信息id为1的职位信息,会报异常

这个报错信息id为1的信息在其他表的外键已经包含了,这边报错是很正常的,对于程序员而言是要定位问题没有问题,但是给用户看的话,用户是看不懂这个问题的,我们应该给用户一个友好的提示信息,但是我们的程序可以会有那么多的异常,不可能每个异常都会判断到给用户一个友好的提示,这个时候怎么办呢?我们需要做一个全局异常处理,我们需要知道系统异常包括两个,一个编译时的异常、运行时的异常,编译时的异常一般通过try catch去捕获,运行时异常通过规范代码开发或者测试去减少运行时异常,在springmvc里能把所有异常类型处理啊,从各种处理过程中解耦出来,既保证相关处理过程比较单一也实现了异常处理信息的统一维护 ,这就是全局异常处理

全局处理异常主要有两种处理方式,第一种通过ControllerAdvice注解ExceptionHandler注解

第二种通过ErrorController类去实现

他们是有区别的第一个通过ControllerAdvice这个注解,只能处理控制器抛出的异常,异常已经进入到控制器了

ErrorController类呢能处理所有的异常,包括异常还没进入到控制器的

一般在项目中这两种方式是共同存在的,通过ControllerAdvice去处理控制器的异常,通过ErrorContrller类去处理,未进入控制器的异常

ControllerAdvice可以定义多个拦截方法拦截不同的异常类,并且可以获取抛出的异常信息

下面完善全局异常

 

package com.xxxx.server.exception;

import com.xxxx.server.pojo.RespBean;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;

/**
 * 全局异常
 *
 * @author zhanglishen
 */

@RestControllerAdvice //控制器异常增强类
public class GlobalException {

    //sql语句的异常
    @ExceptionHandler(SQLException.class)
    public RespBean mySQLException(SQLException e){
        if (e instanceof SQLIntegrityConstraintViolationException){
            return RespBean.error("该数据有关数据,操作失败!");
        }
        return RespBean.error("数据库异常,操作失败!");
    }
}

再去删除id=1 

 (6)职称管理功能实现

职位管理和职称管理都差不多,都属于系统设计里面的相关业务逻辑

职称管理表t_joblevel

 

对创建日期做格式化在pojo类Joblevel:

 

 JoblevelController:添加接口,创建方法

package com.xxxx.server.controller;


import com.xxxx.server.pojo.Joblevel;
import com.xxxx.server.pojo.Position;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.service.IJoblevelService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@RestController
@RequestMapping("/system/basic/joblevel")
public class JoblevelController {

    @Autowired
    private IJoblevelService joblevelService;

    @ApiOperation(value = "获取所有职称")
    @GetMapping("/")
    public List<Joblevel> getAllJobLevels(){
        return joblevelService.list();
    }


    @ApiOperation(value = "添加职称")
    @PostMapping("/")
    public RespBean addJobLevel(@RequestBody Joblevel joblevel){
        joblevel.setCreateDate(LocalDateTime.now());
        if (joblevelService.save(joblevel)){
            return RespBean.success("添加成功");
        }
        return RespBean.error("添加失败");
    }

    @ApiOperation(value = "更新职称")
    @PutMapping("/")
    public RespBean updateJobLevel(@RequestBody Joblevel joblevel){
        if (joblevelService.updateById(joblevel)){
            return RespBean.success("更新成功");
        }
        return RespBean.error("更新失败");
    }

    @ApiOperation(value = "删除职称")
    @DeleteMapping("/{id}")
    public RespBean deleteJobLevel(@PathVariable Integer id){
        if (joblevelService.removeById(id)){
            return RespBean.success("删除成功");
        }
        return RespBean.error("删除失败");
    }

    @ApiOperation(value = "批量删除职称")
    @DeleteMapping("/")
    public RespBean deleteJobLevelByIds(Integer[] ids){
        if (joblevelService.removeByIds(Arrays.asList(ids))){
            return RespBean.success("删除成功");
        }
        return RespBean.error("删除失败");
    }

}

 

添加职称:

 

数据库:

 

更新职称:

更新刚才创建的id=9 

 

 

 获取所有职称:

批量删除、删除职称:

  (7)权限组角色功能实现

权限组,我们之前在讲权限的时候,用户可以通过用户角色表和用户表关联,进而分配用户不同的角色,也可以通过菜单角色跟菜单表关联进而分配不同的角色,可以拥有不同的菜单权限,那么现在权限组模块啊主要进行角色的相关操作,包括我们的一个角色里面所关联的一个菜单的一个相关操作, 所以这个模块,除了我们角色相关的操作,比如说查询角色,添加角色、删除角色、还可以对菜单进行相关操作,比如说,查一下这个角色所拥有的菜单是什么,更新一下角色的菜单,获取查询一下完整的菜单,所以权限组的模块呢分为两个部分:第一个部分就是角色功能,第二个就是菜单功能

创建permmissionController:

实现角色功能:前3个方法

package com.xxxx.server.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xxxx.server.pojo.Menu;
import com.xxxx.server.pojo.MenuRole;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.pojo.Role;
import com.xxxx.server.service.IMenuRoleService;
import com.xxxx.server.service.IMenuService;
import com.xxxx.server.service.IRoleService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 权限组
 *
 * @author zhanglishen
 */

@RestController
@RequestMapping("/system/basic/permission")
public class permissionController {

    @Autowired
    private IRoleService roleService;
    @Autowired
    private IMenuService menuService;
    @Autowired
    private IMenuRoleService menuRoleService;

    @ApiOperation(value = "查询所有角色")
    @GetMapping("/")
    public List<Role> getAllRoles(){
        return roleService.list();
    }

    @ApiOperation(value = "添加角色")
    @PostMapping("/role")
    public RespBean addRole(@RequestBody Role role){
        //判断角色开头是否以ROLE_开头
        if (!role.getName().startsWith("ROLE_")){
            role.setName("ROLE_"+role.getName());
        }
        if (roleService.save(role)){
            return RespBean.success("添加成功");
        }
        return RespBean.error("添加失败");
    }

    @ApiOperation(value = "删除角色")
    @DeleteMapping("/role/{rid}")
    public RespBean deleteRole(@PathVariable Integer rid){
        if (roleService.removeById(rid)){
            return RespBean.success("删除成功");
        }
        return RespBean.error("删除失败");
    }

    @ApiOperation(value = "获取所有菜单")
    @GetMapping("/menus")
    private List<Menu> getAllMenus(){
        //菜单查询是有子菜单的,需要自己写sql语句
        return menuService.getAllMenus();
    }

    @ApiOperation(value = "根据角色ID查询菜单ID")
    @GetMapping("/mid/{rid}")
    private List<Integer> getMidByRid(@PathVariable Integer rid){
        return menuRoleService.list(new QueryWrapper<MenuRole>().eq("rid",rid)).stream()
                .map(MenuRole::getMid).collect(Collectors.toList());
    }

    @ApiOperation(value = "更新角色菜单")
    @PutMapping("/")     //第一个参数是角色id:rid 第二个参数是:菜单id数组:mids
    public RespBean updateMenuRole(Integer rid,Integer[] mids){
        return menuRoleService.updateMenuRole(rid,mids);
    }

}

 

添加角色:

 

数据库:t_role角色表 

 

获取所有角色:

 

删除角色:删除刚创建的角色 

 

 

 (8)权限组菜单查询功能实现

IMenuService接口: 

 

package com.xxxx.server.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxxx.server.pojo.Menu;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface IMenuService extends IService<Menu> {

    //根据用户id查询菜单列表
    List<Menu> getMenusByAdminId();

    //根据角色获取菜单列表
    List<Menu> getMenusWithRole();

    //查询所有菜单
    List<Menu> getAllMenus();
}

IMenuServiceImpl:实现类:

 

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.mapper.MenuMapper;
import com.xxxx.server.pojo.Admin;
import com.xxxx.server.pojo.Menu;
import com.xxxx.server.service.IMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //根据用户id查询菜单列表
    @Override
    public List<Menu> getMenusByAdminId() {
        Integer adminId=((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();

        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        // 从redis中获取数据 ,第一次获取不会获取到任何数据
        List<Menu> menus =(List<Menu>) valueOperations.get("menu_" + adminId);

        //如果为空,去数据库中获取
        if (CollectionUtils.isEmpty(menus)){
            menus= menuMapper.getMenusByAdminId(adminId);
            //把获取到的redis放到redis里面
            valueOperations.set("menu_"+adminId,menus);
        }
        return menus;
    }


    //根据角色获取菜单列表
    @Override
    public List<Menu> getMenusWithRole() {
        return menuMapper.getMenusWithRole();
    }

    //查询所有菜单
    @Override
    public List<Menu> getAllMenus() {
        return menuMapper.getAllMenus();
    }
}

MenuMapper接口: 

 

package com.xxxx.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxxx.server.pojo.Menu;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface MenuMapper extends BaseMapper<Menu> {

    //根据用户id查询,菜单列表
    List<Menu> getMenusByAdminId(Integer id);

    //根据角色获取菜单列表
    List<Menu> getMenusWithRole();

    //查询所有菜单
    List<Menu> getAllMenus();
}

 MenuMapper.xml:

 

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxxx.server.mapper.MenuMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.xxxx.server.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, url, path, component, name, iconCls, keepAlive, requireAuth, parentId, enabled
    </sql>

    <resultMap id="Menus" type="com.xxxx.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.xxxx.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>
    
    <resultMap id="MenusWithRole" type="com.xxxx.server.pojo.Menu" extends="BaseResultMap">
        <collection property="roles" ofType="com.xxxx.server.pojo.Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
            <result column="rnameZh" property="nameZh"/>
        </collection>
    </resultMap>
    
    <resultMap id="MenusWithChildren" type="com.xxxx.server.pojo.Menu" extends="BaseResultMap">
        <id column="id1" property="id"/>
        <result column="name1" property="name"/>

        <collection property="children" ofType="com.xxxx.server.pojo.Menu">
            <id column="id2" property="id"/>
            <result column="name2" property="name"/>

            <collection property="children" ofType="com.xxxx.server.pojo.Menu">
                <id column="id3" property="id"/>
                <result column="name3" property="name"/>
            </collection>
        </collection>
    </resultMap>


    <!--根据用户id查询菜单列表-->
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT
            m1.*,
            m2.id AS id2,
            m2.url AS url2,
            m2.path AS path2,
            m2.component AS component2,
            m2.`name` AS `name2`,
            m2.iconCls AS iconCls2,
            m2.keepAlive AS keepAlive2,
            m2.requireAuth AS requireAuth2,
            m2.parentId AS parentId2,
            m2.enabled AS enabled2
        FROM
            t_menu m1,
            t_menu m2,
            t_admin_role ar,
            t_menu_role mr
        WHERE
             m1.id=m2.parentId
        AND
             m2.id=mr.mid
        AND
             mr.rid=ar.rid
        AND
             ar.adminId=#{id}
        AND
             m2.enabled=TRUE
        ORDER BY
             m2.id
    </select>

    <!--根据角色获取菜单列表-->
    <select id="getMenusWithRole" resultMap="MenusWithRole">
        SELECT
            m.*,
            r.id AS rid,
            r.`name` AS rname,
            r.nameZh AS rnameZh
        FROM
            t_menu m,
            t_menu_role mr,
            t_role r
        WHERE
            m.id=mr.mid
        AND
            r.id=mr.rid
        ORDER BY
            m.id
    </select>
    
    <!--查询所有菜单-->
    <select id="getAllMenus" resultMap="MenusWithChildren">
        SELECT
            m1.id AS id1,
            m1.`name` AS name1,
            m2.id AS id2,
            m2.`name` AS name2,
            m3.id AS id3,
            m3.`name` AS name3
        FROM
            t_menu m1,
            t_menu m2,
            t_menu m3
        WHERE
            m1.id=m2.parentId
        AND
            m2.id=m3.parentId
        AND
            m3.enabled=TRUE

    </select>

</mapper>

查询所有菜单:

 

 

 

(9)权限组菜单更新功能实现

IMenuRoleSeervice:接口 

package com.xxxx.server.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxxx.server.pojo.MenuRole;
import com.xxxx.server.pojo.RespBean;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface IMenuRoleService extends IService<MenuRole> {

    //更新角色菜单
    RespBean updateMenuRole(Integer rid, Integer[] mids);
}

 实现类:

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.mapper.MenuRoleMapper;
import com.xxxx.server.pojo.MenuRole;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.service.IMenuRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class MenuRoleServiceImpl extends ServiceImpl<MenuRoleMapper, MenuRole> implements IMenuRoleService {

    @Autowired
    private MenuRoleMapper menuRoleMapper;

    //更新角色菜单
    @Override
    @Transactional
    public RespBean updateMenuRole(Integer rid, Integer[] mids) {
        //先删除这个角色id下的所有菜单id
        menuRoleMapper.delete(new QueryWrapper<MenuRole>().eq("rid",rid));
        if (null==mids || 0==mids.length){
            return RespBean.success("更新成功");
        }
        Integer result = menuRoleMapper.insertRecord(rid, mids);
        if (result==mids.length){
            return RespBean.success("更新成功");
        }
        return RespBean.error("更新失败");
    }
}

MenuRoleMapper接口:

package com.xxxx.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxxx.server.pojo.MenuRole;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface MenuRoleMapper extends BaseMapper<MenuRole> {

    //更新角色菜单
    Integer insertRecord(@Param("rid") Integer rid,@Param("mids") Integer[] mids);
}

MenuRoleMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxxx.server.mapper.MenuRoleMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.xxxx.server.pojo.MenuRole">
        <id column="id" property="id" />
        <result column="mid" property="mid" />
        <result column="rid" property="rid" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, mid, rid
    </sql>

    <!--更新角色菜单-->
    <insert id="insertRecord">
        insert into t_menu_role(mid,rid) values
        <foreach collection="mids" item="mid" separator=",">
            (#{mid},#{rid})
        </foreach>
    </insert>

</mapper>

根据角色id查询菜单id:

 

更改角色菜单:

在查询: 

 再更改过来:

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喵俺第一专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值