目录
一、整合前的配置
1.1 导入shiro依赖
1.2 config配置
1.2.1 ShiroConfig(⭐)
1.2.2 MyConfig(拦截器配置)
3. 拦截器(LoginInterceptor)
二、认证登录
2.1. controller
2.2 service和serviceImpl(不用)
2.3 mapper
2.4 自定义的shiro和realm
三、权限授权验证
3.1. controller(测试接口的权限)
3.2 自定义的shiro和realm
四、动态菜单的实现
4.1 目标效果
4.2 查询所有的权限
4.3 不同的用户显示不同的菜单(权限)
一、整合前的配置
1.1 导入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
1.2 config配置
1.2.1 ShiroConfig(⭐)
配置shiro的一些基本配置,需要注意--设置安全管理器securityManager与你的项目有关,
这里我设置了登录时需要的自定义myRealm,和权限授权验证前的自定义uuidRealm。
package com.itqq.config;
import com.itqq.realm.MyRealm;
import com.itqq.realm.UUIDRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Author: sys
* @Description:shiro整合Spring的配置文件
*/
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
@Autowired
private UUIDRealm uuidRealm;
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return new AuthorizationAttributeSourceAdvisor();
}
// 设置安全管理器securityManager
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
List<Realm> realms = new ArrayList<>();
realms.add(myRealm);
realms.add(uuidRealm);
securityManager.setRealms(realms);
return securityManager;
}
// 设置filter
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager( securityManager());
shiroFilter.setLoginUrl("/anon");
shiroFilter.setUnauthorizedUrl("/anon");
return shiroFilter;
}
// @Bean
// public MyRealm myRealm(){
// return new MyRealm();
// }
}
1.2.2 MyConfig(拦截器配置)
因为每次权限验证前都需要验证该账号是否登录,所以使用拦截器
package com.itqq.config;
import com.itqq.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将loginInterceptor这个拦截器注册到Spring Boot中,并排除对/employee/login(登录)这个路径的拦截
registry.addInterceptor(loginInterceptor).excludePathPatterns("/employee/login");
}
}
3. 拦截器(LoginInterceptor)
将重复的代码抽取出来,此时,用户已经登录过一次,需要再次认证
package com.itqq.interceptor;
import com.itqq.shiro.UUIDToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:使用过滤器用户在进行权限验证时必须要先认证,一个个加太麻烦
* 所以在拦截器中统一进行认证
*
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
String token = request.getHeader("token");
// 使用Shiro框架的SecurityUtils.getSubject()方法获取当前的Subject(即用户身份)
Subject subject = SecurityUtils.getSubject();
// 将token传给UUIDRealm进行认证
subject.login(new UUIDToken(token));
}catch (Exception e){
e.printStackTrace();
}
return true;
}
}
二、认证登录
使用shiro框架自带的组件进行登录验证,不用用户的service和serviceImpl
2.1. controller
package com.itqq.controller;
import com.itqq.pojo.Employee;
import com.itqq.pojo.Result;
import com.itqq.service.EmployeeService;
import com.itqq.shiro.PhonePasswordToken;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
@RestController
@RequestMapping("employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 登录接口
* @param token
* @return
*/
@PostMapping("login")
public Result login(@RequestBody PhonePasswordToken token) {
// 此token是前端传来的登录信息,这里是phone和password
SecurityUtils.getSubject().login(token);
String tokenString = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(tokenString, SecurityUtils.getSubject().getPrincipal());
return Result.success(tokenString);
}
@GetMapping("")
public Result findAll() {
return Result.success(employeeService.findAll());
}
@GetMapping("findById/{id}")
public Result findById(@PathVariable Integer id) {
return Result.success(employeeService.findById(id));
}
/**
* 添加员工
* @param employee
* @return
*/
@PostMapping("add")
public Result add(@RequestBody Employee employee) {
return employeeService.add(employee) ? Result.success("添加成功") : Result.failed("添加失败");
}
@PostMapping("update")
public Result update(@RequestBody Employee employee) {
return employeeService.update(employee) ? Result.success("修改成功") : Result.failed("修改失败");
}
@DeleteMapping("delete")
public Result delete(Integer id) {
return employeeService.delete(id) ? Result.success("删除成功") : Result.failed("删除失败");
}
@DeleteMapping("deleteids")
public Result deleteids(Integer[] ids) {
return employeeService.deleteids(ids) ? Result.success("删除成功") : Result.failed("删除失败");
}
}
2.2 service和serviceImpl(不用)
package com.itqq.service;
import com.itqq.pojo.Employee;
import com.itqq.pojo.Result;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
public interface EmployeeService {
List<Employee> findAll();
Employee findById(Integer id);
boolean add(Employee employee);
boolean update(Employee employee);
boolean delete(Integer id);
boolean deleteids(Integer[] ids);
Employee findEmployeeByName(String name);
Employee findEmployeeByPhone(Long phone);
}
增加用户信息的时候,密码进行MD5加密 ,盐是动态盐
package com.itqq.service.impl;
import com.itqq.mapper.EmployeeMapper;
import com.itqq.pojo.Employee;
import com.itqq.service.EmployeeService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public List<Employee> findAll() {
return employeeMapper.findAll();
}
@Override
public Employee findById(Integer id) {
return employeeMapper.findById(id);
}
@Override
public boolean add(Employee employee) {
// 添加的时候设置动态盐
String slat = UUID.randomUUID().toString().replace("-", "");
// 密码加密存储
String password = String.valueOf(new SimpleHash("MD5", employee.getPassword(), slat, 1024));
// 获取当前登录人的id
Integer principal = (Integer) SecurityUtils.getSubject().getPrincipal();
employee.setPassword(password);
employee.setSalt(slat);
employee.setCreateTime(new Date());
employee.setCreateId(principal);
return employeeMapper.add(employee);
}
@Override
public boolean update(Employee employee) {
return employeeMapper.update(employee);
}
@Override
public boolean delete(Integer id) {
return employeeMapper.delete(id);
}
@Override
public boolean deleteids(Integer[] ids) {
return employeeMapper.deleteids(ids);
}
@Override
public Employee findEmployeeByName(String name) {
return employeeMapper.findEmployeeByName(name);
}
@Override
public Employee findEmployeeByPhone(Long phone) {
return employeeMapper.findEmployeeByPhone(phone);
}
}
2.3 mapper
package com.itqq.mapper;
import com.itqq.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
public interface EmployeeMapper {
List<Employee> findAll();
Employee findById(Integer id);
boolean add(Employee employee);
boolean update(Employee employee);
boolean delete(Integer id);
boolean deleteids(Integer[] ids);
Employee findEmployeeByName(String name);
Employee findEmployeeByPhone(Long phone);
}
<?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.itqq.mapper.EmployeeMapper">
<insert id="add">
insert into sys_employee(name,password,phone,salt,status,create_id,create_time)
values(#{name},#{password},#{phone},#{salt},#{status},#{createId},#{createTime})
</insert>
<update id="update">
update sys_employee
<set>
<if test="name != null">
name = #{name},
</if>
<if test="password != null">
password = #{password},
</if>
<if test="phone != null">
phone = #{phone},
</if>
<if test="salt != null">
salt = #{salt},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="createId != null">
create_id = #{createId},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
</set>
</update>
<delete id="delete">
delete from sys_employee where id = #{id}
</delete>
<delete id="deleteids">
delete from sys_employee where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="findAll" resultType="com.itqq.pojo.Employee">
select * from sys_employee
</select>
<select id="findById" resultType="com.itqq.pojo.Employee">
select * from sys_employee where id = #{id}
</select>
<select id="findEmployeeByName" resultType="com.itqq.pojo.Employee">
select * from sys_employee where name = #{name}
</select>
<select id="findEmployeeByPhone" resultType="com.itqq.pojo.Employee">
select * from sys_employee where phone = #{phone}
</select>
</mapper>
2.4 自定义的shiro和realm
package com.itqq.realm;
import com.itqq.pojo.Employee;
import com.itqq.service.EmployeeService;
import com.itqq.shiro.PhonePasswordToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:自定义的MyRealm用于认证使用
*/
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private EmployeeService employeeService;
public MyRealm() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(1024);
this.setCredentialsMatcher(hashedCredentialsMatcher);
}
// 判断传入的AuthenticationToken对象是否是PhonePasswordToken类型的实例
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof PhonePasswordToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("-----------------开始认证!!!!");
// 接收用户输入的账号(手机号)和密码
Long phone = (Long) token.getPrincipal();
String password = (String) token.getCredentials();
if(phone == null){
throw new AuthenticationException("账号不能为空");
}
if(!StringUtils.hasText(password)){
throw new AuthenticationException("密码不能为空");
}
// 从数据库中查询对象
Employee employee = employeeService.findEmployeeByPhone(phone);
if(employee == null){
throw new AuthenticationException("用户名不存在");
}
System.out.println(employee.getSalt());
System.out.println(employee.getName());
System.out.println(employee.getPassword());
return new SimpleAuthenticationInfo(employee.getId(),employee.getPassword(),ByteSource.Util.bytes(employee.getSalt()),getName());
}
}
自定义的shiro组件接收 前端传来的登录信息
package com.itqq.shiro;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.shiro.authc.AuthenticationToken;
import java.io.Serializable;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PhonePasswordToken implements AuthenticationToken, Serializable {
private static final long serialVersionUID = -240712713650948393L;
private Long phone;
private String password;
@Override
public Object getPrincipal() {
return phone;
}
@Override
public Object getCredentials() {
return password;
}
}
三、权限授权验证
认证之后,每次权限验证前需要再次进行登录验证(具体配置见config拦截器配置)
3.1. controller(测试接口的权限)
package com.itqq.controller;
import com.itqq.pojo.Result;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created with IntelliJ IDEA.
*
* @Author:syq
* @Description:授权测试
*/
@RestController
@RequestMapping("welcome")
public class WelcomeController {
/*
1. 游客接口
2. 认证接口
3. 游客/认证接口
4. 授权接口
*/
@GetMapping("1.do")
public Result m1(){
return Result.success("1.游客接口!!!");
}
// @RequiresUser需要用户认证才能访问的方法或类,要token
@RequiresUser
@GetMapping("2.do")
public Result m2(/*@RequestHeader String token*/){
Subject subject = SecurityUtils.getSubject();
// subject.login(new UUIDToken(token));
return Result.success("2.认证接口!!!" + subject.getPrincipals());
}
@RequestMapping("3.do")
public Result m3(){
Subject subject = SecurityUtils.getSubject();
if(subject.isAuthenticated()){
return Result.success("3.认证接口!!!" + subject.getPrincipals());
}else{
return Result.failed("3.游客接口!!!");
}
}
// @RequiresRoles注解,表示该接口需要在具有特定角色的用户才能访问
@RequiresRoles("管理员")
@RequestMapping("4.do")
public Result m4(){
Subject subject = SecurityUtils.getSubject();
return Result.success("4-1.授权角色接口" + subject.getPrincipals());
}
@RequiresPermissions("sys:employee:insert")
@RequestMapping("5.do")
public Result m5(){
Subject subject = SecurityUtils.getSubject();
return Result.success("5-2.授权权限接口" + subject.getPrincipals());
}
// @RequiresRoles({"管理员","管理员2"})--两个角色都要满足
@RequiresRoles(value = {"管理员","超级管理员"},logical = Logical.OR)//只用满足一个条件
@RequestMapping("6.do")
public Result m6(){
Subject subject = SecurityUtils.getSubject();
return Result.success("6-2.授权权限接口" + subject.getPrincipals());
}
}
service和serviceImpl和上述一样这里就不在赘述
3.2 自定义的shiro和realm
将当前账号有关的角色信息和权限信息添加到shiro组件中,用于判断
package com.itqq.realm;
import com.itqq.mapper.PermissionMapper;
import com.itqq.service.EmployeeService;
import com.itqq.service.PermissionService;
import com.itqq.service.RoleService;
import com.itqq.shiro.UUIDToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
@Component
@Slf4j
public class UUIDRealm extends AuthorizingRealm {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
//用于判断传入的AuthenticationToken对象是否是UUIDToken的实例。如果是,则返回true,表示支持该令牌;否则返回false,表示不支持该令牌。
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UUIDToken;
}
// 该函数调用了roleService.findRoleNameByEmployeeId(id)方法,
// 将返回值添加到info对象的roles属性中。
// findRoleNameByEmployeeId方法根据员工ID查找对应的职位名称
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Integer id = (Integer) principalCollection.getPrimaryPrincipal();
log.info("id:{}",id);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roleService.findRoleNameByEmployeeId(id));
info.addStringPermissions(permissionService.findPermissionNameByEmployeeId(id));
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 从令牌中提取UUID作为凭证
String uuid = (String) token.getCredentials();
if(!StringUtils.hasText(uuid)){
throw new RuntimeException("token不能为空");
}
// 从resdis中获取员工id
Integer id = (Integer)redisTemplate.opsForValue().get(uuid);
if(id == null){
throw new RuntimeException("token已经失效");
}
// 构建认证信息对象,包含用户ID、UUID和名称
// 将员工id,uuid(生成的),名称送去校验
return new SimpleAuthenticationInfo(id,uuid,getName());
}
}
package com.itqq.shiro;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.shiro.authc.AuthenticationToken;
import java.io.Serializable;
/**
* Created with IntelliJ IDEA.
*
* @Author: syq
* @Description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UUIDToken implements AuthenticationToken, Serializable {
private static final long serialVersionUID = 8884806189738587998L;
private String uuid;
@Override
public Object getPrincipal() {
return uuid;
}
@Override
public Object getCredentials() {
return uuid;
}
}
四、动态菜单的实现
4.1 目标效果
数据库设计
动态菜单需要根据查看用户的所有权限,主要是要进行树状显示查询
4.2 查询所有的权限
1. Controller
// 查询全部数据
List<Permission> findByParentId(Integer id);
2. service和serviceImpl
List<Permission> findAll();
public List<Permission> findAll() {
return permissionMapper.findByParentId(0);
}
3. mapper
// 查询全部数据
List<Permission> findByParentId(Integer id);
先运行findByParentId,运行一次后,结果映射又递归调用此时将id赋值parentId
<resultMap id="permission" type="Permission">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="path" property="path"/>
<result column="permission" property="permission"/>
<result column="icon" property="icon"/>
<result column="parent_id" property="parentId"/>
<result column="create_id" property="createId"/>
<result column="create_time" property="createTime"/>
<result column="index" property="index"/>
<collection property="children" ofType="Permission" select="findByParentId" column="id"></collection>
</resultMap>
<select id="findByParentId" resultMap="permission">
select * from sys_permission where parent_id = #{parentId}
</select>
4.3 不同的用户显示不同的菜单(权限)
1. controller
// 获取当前用户的菜单
@RequiresUser
@GetMapping("menu")
public Result findMenu(){
return Result.success(permissionService.findMenu());
}
2. service和serviceImpl
List<Permission> findMenu(
public List<Permission> findMenu() {
Integer eid = (Integer) SecurityUtils.getSubject().getPrincipal();
return permissionMapper.findMenu(eid, 0);
}
3. mapper
// 根据不同的用户查询数据
List<Permission> findMenu(@Param("eid") Integer eid,@Param("parentId") Integer parentId);
<resultMap id="permission1" type="Permission">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="path" property="path"/>
<result column="permission" property="permission"/>
<result column="icon" property="icon"/>
<result column="parent_id" property="parentId"/>
<result column="create_id" property="createId"/>
<result column="create_time" property="createTime"/>
<result column="index" property="index"/>
<collection property="children" ofType="Permission" select="findMenu" column="{eid=eid,parentId=id}"></collection>
</resultMap>
<select id="findMenu" resultMap="permission1">
SELECT DISTINCT p.*,eid from sys_permission p
LEFT JOIN sys_role_permission rp on p.id = rp.pid
LEFT JOIN sys_employee_role er on er.rid = rp.rid
where eid = #{eid} and parent_id = #{parentId} and permission is null
order by p.index asc
</select>
4. pojo
@Data
public class Permission implements Serializable {
private static final long serialVersionUID = -9189388242103298381L;
private Integer id;
private String name;
private String permission;
private String path;
private String icon;
private Integer parentId;
private Integer createId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Timestamp createTime;
private Integer index;
private List<Permission> children;
}
总结完毕!!!