目录:
(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.nameZhFROM
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:
更改角色菜单:
在查询:
再更改过来: