一、 数据库表,和实体类
二、菜单管理TODO
最终结果
1. 实体类
通过Mybatis-plus插件代码生成器,自动生成,然后添加一个字段children
import com. baomidou. mybatisplus. annotation. IdType ;
import com. baomidou. mybatisplus. annotation. TableId ;
import com. baomidou. mybatisplus. annotation. TableField ;
import java. io. Serializable ;
import java. util. List ;
import io. swagger. annotations. ApiModel ;
import io. swagger. annotations. ApiModelProperty ;
import lombok. Data ;
import lombok. EqualsAndHashCode ;
import lombok. experimental. Accessors ;
@Data
@EqualsAndHashCode ( callSuper = false )
@Accessors ( chain = true )
@ApiModel ( value= "DdMenu对象" , description= "" )
public class DdMenu implements Serializable {
private static final long serialVersionUID= 1L ;
@ApiModelProperty ( value = "id" )
@TableId ( value = "id" , type = IdType . AUTO)
private Integer id;
@ApiModelProperty ( value = "父菜单id" )
private Integer pid;
@ApiModelProperty ( value = "菜单名" )
private String name;
@ApiModelProperty ( value = "菜单类型(目录,按钮)" )
private String type;
@ApiModelProperty ( value = "菜单权限" )
private String permission;
@ApiModelProperty ( value = "url" )
private String url;
@ApiModelProperty ( value = "path" )
private String path;
@ApiModelProperty ( value = "组件" )
private String component;
@ApiModelProperty ( value = "图标" )
@TableField ( "iconCls" )
private String iconCls;
@ApiModelProperty ( value = "是否保持激活" )
@TableField ( "keepAlive" )
private Boolean keepAlive;
@ApiModelProperty ( value = "是否要求权限" )
@TableField ( "requireAuth" )
private Boolean requireAuth;
@ApiModelProperty ( value = "是否启用" )
private Boolean enabled;
@ApiModelProperty ( value = "子菜单" )
@TableField ( exist = false )
private List < DdMenu > children;
}
2. Redis 缓存
启动redis 添加依赖
< ! -- redis-- >
< dependency>
< groupId> org. springframework. boot< / groupId>
< artifactId> spring- boot- starter- data- redis< / artifactId>
< / dependency>
< ! -- commons- pool2 对象池依赖-- >
< dependency>
< groupId> org. apache. commons< / groupId>
< artifactId> commons- pool2< / artifactId>
< / dependency>
redis配置
spring :
redis :
host : 127.0.0.1
port : 6379
database : 0
timeout : 1800000
lettuce :
pool :
max-active : 1024
max-wait : -1
max-idle : 200
min-idle : 5
Redis配置类
import org. springframework. context. annotation. Bean ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. data. redis. connection. RedisConnectionFactory ;
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 ( RedisConnectionFactory redisConnectionFactory) {
RedisTemplate < String , Object > redisTemplate = new RedisTemplate < > ( ) ;
redisTemplate. setKeySerializer ( new StringRedisSerializer ( ) ) ;
redisTemplate. setValueSerializer ( new GenericJackson2JsonRedisSerializer ( ) ) ;
redisTemplate. setHashKeySerializer ( new StringRedisSerializer ( ) ) ;
redisTemplate. setHashValueSerializer ( new GenericJackson2JsonRedisSerializer ( ) ) ;
redisTemplate. setConnectionFactory ( redisConnectionFactory) ;
return redisTemplate;
}
}
3. 编写逻辑,查询菜单
controller
import com. dd. security. entity. DdMenu ;
import com. dd. security. service. DdMenuService ;
import com. dd. security. service. DdUserService ;
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. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
import java. util. List ;
@RestController
@RequestMapping ( "/security/dd-menu" )
public class DdMenuController {
@Autowired
private DdMenuService ddMenuService;
@ApiOperation ( value = "通过用户id查询菜单列表" )
@GetMapping ( "/menu" )
public List < DdMenu > getMenusByUserId ( ) {
return ddMenuService. getMenusByUserId ( ) ;
}
}
service
import com. dd. security. entity. DdMenu ;
import com. dd. security. entity. DdUser ;
import com. dd. security. mapper. DdMenuMapper ;
import com. dd. security. service. DdMenuService ;
import com. baomidou. mybatisplus. extension. service. impl. ServiceImpl ;
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. awt. * ;
import java. util. List ;
@Service
public class DdMenuServiceImpl extends ServiceImpl < DdMenuMapper , DdMenu > implements DdMenuService {
@Autowired
private DdMenuMapper ddMenuMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public List < DdMenu > getMenusByUserId ( ) {
DdUser user = ( DdUser ) SecurityContextHolder . getContext ( ) . getAuthentication ( ) . getPrincipal ( ) ;
ValueOperations < String , Object > valueOperations = redisTemplate. opsForValue ( ) ;
List < DdMenu > menus = ( List < DdMenu > ) valueOperations. get ( "menu_" + user. getId ( ) ) ;
if ( CollectionUtils . isEmpty ( menus) ) {
menus = ddMenuMapper. getMenusByUserId ( user. getId ( ) ) ;
valueOperations. set ( "menu_" + user. getId ( ) , menus) ;
}
return menus;
}
}
mapper
select
distinct
m1. * ,
m2. id as id2,
m2. pid as pid2,
m2. ` name` as name2,
m2. type as type2,
m2. permission as permission2,
m2. url as url2,
m2. path as path2,
m2. component as component2,
m2. iconCls as iconCls2,
m2. keepAlive as keepAlive2,
m2. requireAuth as requireAuth,
m2. enabled as enabled2
from
dd_menu as m1
inner join
dd_menu as m2
on
m1. id = m2. pid
inner join
dd_role_menu as rm
on
rm. mid = m2. id
inner join
dd_user_role as ur
on
ur. rid = rm. rid
where
ur. uid = 1
<?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.dd.security.mapper.DdMenuMapper" >
< resultMap id = " BaseResultMap" type = " com.dd.security.entity.DdMenu" >
< id column = " id" property = " id" />
< result column = " pid" property = " pid" />
< result column = " name" property = " name" />
< result column = " type" property = " type" />
< result column = " permission" property = " permission" />
< result column = " url" property = " url" />
< result column = " path" property = " path" />
< result column = " component" property = " component" />
< result column = " iconCls" property = " iconCls" />
< result column = " keepAlive" property = " keepAlive" />
< result column = " requireAuth" property = " requireAuth" />
< result column = " enabled" property = " enabled" />
</ resultMap>
< resultMap id = " Menus" type = " com.dd.security.entity.DdMenu" extends = " BaseResultMap" >
< collection property = " children" ofType = " com.dd.security.entity.DdMenu" >
< id column = " id2" property = " id" />
< result column = " pid2" property = " pid" />
< result column = " name2" property = " name" />
< result column = " type2" property = " type" />
< result column = " permission2" property = " permission" />
< result column = " url2" property = " url" />
< result column = " path2" property = " path" />
< result column = " component2" property = " component" />
< result column = " iconCls2" property = " iconCls" />
< result column = " keepAlive2" property = " keepAlive" />
< result column = " requireAuth2" property = " requireAuth" />
< result column = " enabled2" property = " enabled" />
</ collection>
</ resultMap>
< select id = " getMenusByUserId" resultMap = " Menus" >
select
distinct
m1.*,
m2.id as id2,
m2.pid as pid2,
m2.`name` as name2,
m2.type as type2,
m2.permission as permission2,
m2.url as url2,
m2.path as path2,
m2.component as component2,
m2.iconCls as iconCls2,
m2.keepAlive as keepAlive2,
m2.requireAuth as requireAuth,
m2.enabled as enabled2
from
dd_menu as m1
inner join
dd_menu as m2
on
m1.id = m2.pid
inner join
dd_role_menu as rm
on
rm.mid = m2.id
inner join
dd_user_role as ur
on
ur.rid = rm.rid
where
ur.uid = #{id}
</ select>
</ mapper>
三、url角色权限
我们如果只控制菜单,用户登录后进入管理系统,只能看到自己角色权限对应的菜单,但是如果这个用户此时通过某种手段获取了我们后台的接口对应的url,那么他就算前端看不到相应的菜单和按钮,无法通过事件访问后端接口。也可以直接通过浏览器地址栏输入url,因为它已经登录过,这时,就可以直接访问url了,尽管我们不想让他具有访问这些接口的权限,让他看不见相应的菜单和按钮,此时也只能眼睁睁看着他获取数据
如何处理这种情况呢,我们的菜单表有一个url字段,记录了url地址
我们在菜单实体类中,添加访问此url和菜单,需要的角色、权限属性 规定,菜单表中的url都需要特定的角色和权限才能访问,取余的url,可以允许没有角色或拥有LOGIN_ROLE默认角色的用户访问。 当用户访问后端时,先过滤请求,判断用户请求的是否是菜单表的url,如果是,判断是否具有相应权限,没有权限拦截请求,返回权限不足。如果不是菜单表url,为其添加默认角色LOGIN_ROLE角色
访问需要ROLE_admin角色的路径可以正常访问 访问需要其它角色的url提示权限不足 访问不需要授权的路径,不会触发过滤,不会拦截判断url需要的权限
1. url权限,在全局中设置用户访问的url,需要哪些权限
1. 实现获取每个菜单所需角色权限
实现根据角色获取菜单列表,就是每个菜单有哪些角色有权限
修改菜单实体类,添加角色属性 service接口 mapper
< ! -- 子菜单,继承上面DdMenu 映射对象-- >
< resultMap id= "MenusWithRoler" type= "com.dd.security.entity.DdMenu" extends = "BaseResultMap" >
< collection property= "roles" ofType= "com.dd.security.entity.DdRole" >
< id column= "rid" property= "id" / >
< result column= "rname" property= "name" / >
< result column= "rnameZh" property= "nameZH" / >
< / collection>
< / resultMap>
< select id= "getMenusWithRole" resultMap= "MenusWithRoler" >
select
m. *,
r. id as rid,
r. `name` as rname,
r. name_zh as rnameZh
from
dd_menu as m
inner join
dd_role_menu as rm
on
m. id = rm. mid
inner join
dd_role as r
on
r. id = rm. rid
order by m. id
< / select>
2. SpringSecurity 过滤器,拦截请求,获取url,判断url需要哪些角色权限
截图中忘了加@Component注解
import com. dd. security. entity. DdMenu ;
import com. dd. security. entity. DdRole ;
import com. dd. security. service. DdMenuService ;
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. util. AntPathMatcher ;
import java. util. Collection ;
import java. util. List ;
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private DdMenuService ddMenuService;
private AntPathMatcher antPathMatcher = new AntPathMatcher ( ) ;
@Override
public Collection < ConfigAttribute > getAttributes ( Object o) throws IllegalArgumentException {
FilterInvocation filterInvocation = ( FilterInvocation ) o;
String requestUrl = filterInvocation. getRequestUrl ( ) ;
List < DdMenu > menusWithRole = ddMenuService. getMenusWithRole ( ) ;
for ( DdMenu m : menusWithRole) {
if ( antPathMatcher. match ( m. getUrl ( ) , requestUrl) ) {
String [ ] roles = m. getRoles ( ) . stream ( ) . map ( DdRole :: getName ) . toArray ( String [ ] :: new ) ;
return SecurityConfig . createList ( roles) ;
}
}
return SecurityConfig . createList ( "ROLE_LOGIN" ) ;
}
@Override
public Collection < ConfigAttribute > getAllConfigAttributes ( ) {
return null ;
}
@Override
public boolean supports ( Class < ? > aClass) {
return false ;
}
}
2. 获取用户角色,判断用户角色权限能否访问url
1. 实现获取角色
实体类
import com. baomidou. mybatisplus. annotation. IdType ;
import com. baomidou. mybatisplus. annotation. TableField ;
import com. baomidou. mybatisplus. annotation. TableId ;
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. util. Collection ;
import java. util. List ;
import java. util. stream. Collectors ;
@Data
@EqualsAndHashCode ( callSuper = false )
@Accessors ( chain = true )
@ApiModel ( value= "DdUser对象,使用Spring Security框架就要继承UserDetails接口,实现方法,将返回值改为true" , description= "" )
public class DdUser implements UserDetails {
private static final long serialVersionUID= 1L ;
@ApiModelProperty ( value = "id" )
@TableId ( value = "id" , type = IdType . ID_WORKER_STR)
private Integer id;
private String username;
private String password;
@ApiModelProperty ( value = "用户角色" )
@TableField ( exist = false )
private List < DdRole > roles;
@Override
public Collection < ? extends GrantedAuthority > getAuthorities ( ) {
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 true ;
}
@Override
public boolean isEnabled ( ) {
return true ;
}
}
service mapper
< ! -- -- >
< select id= "getRolesByUserId" resultType= "com.dd.security.entity.DdRole" parameterType= "java.lang.Integer" >
select
r. id as id,
r. name as name,
r. name_zh as nameZh
from
dd_role as r
inner join
dd_user_role as ur
on
r. id = ur. rid
where
ur. uid = #{ userId}
< / select>
修改获取用户信息 修改自定义UserDetailsService
@Bean
@Override
public UserDetailsService userDetailsService ( ) {
return username-> {
DdUser user = ddUserService. getLoginInfoByUsername ( username) ;
if ( user == null ) {
throw new UsernameNotFoundException ( "用户名或密码不正确" ) ;
}
List < DdRole > rolesByUserId = ddRoleService. getRolesByUserId ( user. getId ( ) ) ;
user. setRoles ( rolesByUserId) ;
return user;
} ;
}
2. 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 java. util. Collection ;
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide ( Authentication authentication, Object o, Collection < ConfigAttribute > collection) throws AccessDeniedException , InsufficientAuthenticationException {
for ( ConfigAttribute configAttribute : collection) {
String needRole = configAttribute. getAttribute ( ) ;
if ( "ROLE_LOGIN" . equals ( needRole) ) {
if ( authentication instanceof AnonymousAuthenticationToken ) {
throw new AccessDeniedException ( "尚未登录,请登录!!!" ) ;
} else {
return ;
}
}
Collection < ? extends GrantedAuthority > authorities = authentication. getAuthorities ( ) ;
for ( GrantedAuthority authority: authorities) {
if ( authority. getAuthority ( ) . equals ( needRole) ) {
return ;
}
}
}
throw new AccessDeniedException ( "权限不足,请联系管理员!!!" ) ;
}
@Override
public boolean supports ( ConfigAttribute configAttribute) {
return false ;
}
@Override
public boolean supports ( Class < ? > aClass) {
return false ;
}
}
3. 配置SpringSecurity,让过滤器生效
说明:这些过滤器不会过滤在Security配置类中放行的路径
引入两个过滤器 动态权限配置两个过滤器
@Override
protected void configure ( HttpSecurity http) throws Exception {
http. csrf ( ) . disable ( )
. sessionManagement ( )
. sessionCreationPolicy ( SessionCreationPolicy . STATELESS)
. and ( )
. authorizeRequests ( )
. anyRequest ( ) . authenticated ( )
. withObjectPostProcessor ( new ObjectPostProcessor < FilterSecurityInterceptor > ( ) {
@Override
public < O extends FilterSecurityInterceptor > O postProcess ( O o) {
o. setAccessDecisionManager ( customUrlDecisionManager) ;
o. setSecurityMetadataSource ( customFilter) ;
return o;
}
} )
. and ( )
. headers ( )
. cacheControl ( )
;
http. addFilterBefore ( jwtAuthencationTokenFilter ( ) , UsernamePasswordAuthenticationFilter . class ) ;
http. exceptionHandling ( )
. accessDeniedHandler ( restAccessDeniedHandler)
. authenticationEntryPoint ( restAuthorizationEntryPoint) ;
}
4. 测试
四、权限组功能实现
1. 角色
SpringSecurity 的角色必须带有ROLE_前缀,否则不会被SpringSecurity捕获
正确的角色名:ROLE_admin、ROLE_adsfasdf 错误的角色名:r_admin、admin、ROLE_
所以当我们添加角色时,要判断用户是否以ROLE_开头,不是就补充上再执行添加逻辑,是就直接执行添加逻辑
Controller
import com. dd. common_utils. Result ;
import com. dd. security. entity. DdRole ;
import com. dd. security. service. DdRoleService ;
import io. swagger. annotations. Api ;
import io. swagger. annotations. ApiOperation ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. web. bind. annotation. * ;
import java. util. List ;
@RestController
@RequestMapping ( "/security/dd-role" )
public class DdRoleController {
@Autowired
private DdRoleService ddRoleService;
@ApiOperation ( "获取所有角色" )
@GetMapping ( "/" )
public Result getAllRoles ( ) {
List < DdRole > list = ddRoleService. list ( ) ;
if ( list. isEmpty ( ) ) {
return Result . error ( ) . message ( "没有获取的任何角色信息!!!" ) ;
}
return Result . ok ( ) . data ( "RoleAllList" , list) ;
}
@ApiOperation ( "添加角色" )
@PostMapping ( "/" )
public Result addRole ( @RequestBody DdRole ddRole) {
if ( ! ddRole. getName ( ) . startsWith ( "ROLE_" ) ) {
ddRole. setName ( "ROLE_" + ddRole. getName ( ) ) ;
}
if ( ddRoleService. save ( ddRole) ) {
return Result . ok ( ) . message ( "添加成功" ) ;
}
return Result . error ( ) . message ( "添加失败" ) ;
}
@ApiOperation ( "删除角色" )
@DeleteMapping ( "/role/{id}" )
public Result deleteRole ( @PathVariable ( value = "id" , name = "id" ) Integer id) {
if ( ddRoleService. removeById ( id) ) {
return Result . ok ( ) . message ( "删除成功" ) ;
}
return Result . error ( ) . message ( "删除失败" ) ;
}
}
2. 菜单
controller
import com. baomidou. mybatisplus. core. conditions. query. QueryWrapper ;
import com. dd. common_utils. Result ;
import com. dd. security. entity. DdMenu ;
import com. dd. security. entity. DdRoleMenu ;
import com. dd. security. service. DdMenuService ;
import com. dd. security. service. DdRoleMenuService ;
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 ;
@RestController
@RequestMapping ( "/security/dd-menu" )
public class DdMenuController {
@Autowired
private DdMenuService ddMenuService;
@Autowired
private DdRoleMenuService ddRoleMenuService;
@ApiOperation ( value = "通过用户id查询菜单列表" )
@GetMapping ( "/menu" )
public List < DdMenu > getMenusByUserId ( ) {
return ddMenuService. getMenusByUserId ( ) ;
}
@ApiOperation ( value = "查询所有菜单" )
@GetMapping ( "/menus" )
public Result getMenus ( ) {
List < DdMenu > list = ddMenuService. list ( ) ;
if ( list. isEmpty ( ) ) {
return Result . error ( ) . message ( "没有菜单!!" ) ;
}
return Result . ok ( ) . data ( "menuAllList" , list) ;
}
@ApiOperation ( value = "根据角色id查询菜单id" )
@GetMapping ( "/menuIdByRoleId/{rid}" )
public Result getMenuIdByRoleId ( @PathVariable Integer rid) {
List < Integer > mids = ddRoleMenuService. list ( new QueryWrapper < DdRoleMenu > ( ) . eq ( "rid" , rid) )
. stream ( ) . map ( DdRoleMenu :: getMid ) . collect ( Collectors . toList ( ) ) ;
if ( mids. isEmpty ( ) ) {
return Result . error ( ) . message ( "获取菜单失败" ) ;
}
return Result . ok ( ) . data ( "menuId" , mids) ;
}
@ApiOperation ( value = "根据角色id查询菜单" )
@GetMapping ( "/menuByRoleId/{rid}" )
public Result getMenuByRoleId ( @PathVariable Integer rid) {
List < DdMenu > list = ddMenuService. getMenuByRoleId ( rid) ;
if ( list. isEmpty ( ) ) {
return Result . error ( ) . message ( "没有菜单!!" ) ;
}
return Result . ok ( ) . data ( "menuList" , list) ;
}
@ApiOperation ( value = "更新角色菜单" )
@PutMapping ( "/" )
public Result updateMenuRole ( Integer rid, Integer [ ] mids) {
return ddMenuService. updateMenuRole ( rid, mids) ;
}
}
service
import com. baomidou. mybatisplus. core. conditions. query. QueryWrapper ;
import com. dd. common_utils. Result ;
import com. dd. security. entity. DdMenu ;
import com. dd. security. entity. DdRoleMenu ;
import com. dd. security. entity. DdUser ;
import com. dd. security. mapper. DdMenuMapper ;
import com. dd. security. mapper. DdRoleMenuMapper ;
import com. dd. security. service. DdMenuService ;
import com. baomidou. mybatisplus. extension. service. impl. ServiceImpl ;
import com. dd. security. service. DdRoleMenuService ;
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. transaction. annotation. Transactional ;
import org. springframework. util. CollectionUtils ;
import java. awt. * ;
import java. util. List ;
@Service
public class DdMenuServiceImpl extends ServiceImpl < DdMenuMapper , DdMenu > implements DdMenuService {
@Autowired
private DdMenuMapper ddMenuMapper;
@Autowired
private DdRoleMenuMapper ddRoleMenuMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public List < DdMenu > getMenusByUserId ( ) {
DdUser user = ( DdUser ) SecurityContextHolder . getContext ( ) . getAuthentication ( ) . getPrincipal ( ) ;
ValueOperations < String , Object > valueOperations = redisTemplate. opsForValue ( ) ;
List < DdMenu > menus = ( List < DdMenu > ) valueOperations. get ( "menu_" + user. getId ( ) ) ;
if ( CollectionUtils . isEmpty ( menus) ) {
menus = ddMenuMapper. getMenusByUserId ( user. getId ( ) ) ;
valueOperations. set ( "menu_" + user. getId ( ) , menus) ;
}
return menus;
}
@Override
public List < DdMenu > getMenusWithRole ( ) {
return ddMenuMapper. getMenusWithRole ( ) ;
}
@Override
public List < DdMenu > getMenuByRoleId ( Integer rid) {
return ddMenuMapper. getMenuByRoleId ( rid) ;
}
@Override
@Transactional
public Result updateMenuRole ( Integer rid, Integer [ ] mids) {
ddRoleMenuMapper. delete ( new QueryWrapper < DdRoleMenu > ( ) . eq ( "rid" , rid) ) ;
if ( null == mids|| mids. length == 0 ) {
return Result . ok ( ) . message ( "更新菜单成功" ) ;
}
ddMenuMapper. insertRecord ( rid, mids) ;
return null ;
}
}
mapper
< insert id = " insertRecord" >
insert into dd_role_menu(rid,mid) values
< foreach collection = " mids" item = " mid" separator = " ," >
(#{rid},#{mid})
</ foreach>
</ insert>
3. 用户
controller
import com. dd. common_utils. Result ;
import com. dd. security. entity. DdMenu ;
import com. dd. security. entity. DdUser ;
import com. dd. security. service. DdUserService ;
import io. swagger. annotations. Api ;
import io. swagger. annotations. ApiOperation ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. security. core. parameters. P ;
import org. springframework. security. crypto. password. PasswordEncoder ;
import org. springframework. web. bind. annotation. * ;
import java. util. List ;
@RestController
@Api ( "传递查询条件时,不要携带security的字段,只携带DdUser对象本身的字段,JSON" )
@RequestMapping ( "/security/dd-user-role" )
public class DdUserRoleController {
@Autowired
private DdUserService ddUserService;
@Autowired
private PasswordEncoder passwordEncoder;
@ApiOperation ( "获取所有操作员以及它拥有的角色和权限,传递查询条件时,不要携带security的字段,只携带DdUser对象本身的字段,JSON" )
@PostMapping ( "/" )
public Result getAllUser ( @RequestBody DdUser ddUser) {
List < DdUser > list = ddUserService. getAllUser ( ddUser) ;
if ( list. isEmpty ( ) ) {
return Result . error ( ) . message ( "没有获取的用户信息!!!" ) ;
}
return Result . ok ( ) . data ( "allUserList" , list) ;
}
@ApiOperation ( "更新用户" )
@PutMapping ( "/" )
public Result updateUser ( @RequestBody DdUser ddUser) {
ddUser. setPassword ( null ) ;
if ( ddUserService. updateById ( ddUser) ) {
return Result . ok ( ) . message ( "修改用户成功!!!" ) ;
}
return Result . error ( ) . message ( "修改失败!!!" ) ;
}
@ApiOperation ( "更新用户拥有的角色" )
@PutMapping ( "/updateUserRole" )
public Result updateUserRole ( Integer uid, Integer [ ] rids) {
return ddUserService. updateUserRole ( uid, rids) ;
}
@ApiOperation ( "修改密码" )
@PutMapping ( "/updatePasswordById/{id}/{password}" )
public Result updatePassword ( @PathVariable Integer id, @PathVariable String password) {
String encode = passwordEncoder. encode ( password) ;
DdUser ddUser = new DdUser ( ) ;
ddUser. setId ( id) ;
ddUser. setPassword ( encode) ;
if ( ddUserService. updateById ( ddUser) ) {
return Result . ok ( ) . message ( "修改密码成功!!!" ) ;
}
return Result . error ( ) . message ( "修改密码失败!!!" ) ;
}
@ApiOperation ( "删除用户" )
@DeleteMapping ( "/{id}" )
public Result deleteById ( @PathVariable Integer id) {
if ( ddUserService. removeById ( id) ) {
return Result . ok ( ) . message ( "删除成功!!!" ) ;
}
return Result . error ( ) . message ( "删除失败!!!" ) ;
}
}
service
@Autowired
private DdUserRoleMapper ddUserRoleMapper;
@Override
public List < DdUser > getAllUser ( DdUser ddUser) {
return ddUserMapper. getAllUser ( ddUser) ;
}
@Override
@Transactional
public Result updateUserRole ( Integer uid, Integer [ ] rids) {
ddUserRoleMapper. delete ( new QueryWrapper < DdUserRole > ( ) . eq ( "uid" , uid) ) ;
if ( null == rids|| rids. length == 0 ) {
return Result . ok ( ) . message ( "更新用户角色成功" ) ;
}
ddUserMapper. insertRecord ( uid, rids) ;
return Result . ok ( ) . message ( "更新菜单成功" ) ;
}
mapper
< ? 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.dd.security.mapper.DdUserMapper" >
< ! -- 结果集封装-- >
< resultMap type= "com.dd.security.entity.DdUser" id= "DdUserResult" >
< result property= "id" column= "id" / >
< result property= "username" column= "username" / >
< result property= "password" column= "password" / >
< / resultMap>
< resultMap id= "DdUserRoleResult" type= "com.dd.security.entity.DdUser" extends = "DdUserResult" >
< collection property= "roles" ofType= "com.dd.security.entity.DdRole" >
< id column= "rid" property= "id" / >
< result column= "rname" property= "name" / >
< result column= "rnameZh" property= "nameZh" / >
< / collection>
< / resultMap>
< select id= "selectByUsername" parameterType= "java.lang.String" resultType= "com.dd.security.entity.DdUser" >
select id, username, password from dd_user where username= #{ username}
< / select>
< select id= "getAllUser" resultMap= "DdUserRoleResult" parameterType= "com.dd.security.entity.DdUser" >
select
u. id as id,
u. username as username,
u. `password` as `password`,
r. id as rid,
r. `name` as rname,
r. name_zh as rnameZh
from
dd_user as u
inner join
dd_user_role as ur
on
u. id = ur. uid
inner join
dd_role as r
on
ur. rid = r. id
< where>
< trim>
< if test= "id != null and id != '' and id!=0" > and u. id = #{ id} < / if >
< if test= "username != null and username != ''" > and u. username like concat ( '%' , #{ username} , '%' ) < / if >
< if test= "roles != null" > and r. name_zh like concat ( '%' , #{ roles[ 0 ] . nameZh} , '%' ) < / if >
< / trim>
< / where>
< / select>
< ! -- 更新用户角色-- >
< insert id= "insertRecord" >
insert into dd_user_role ( uid, rid) values
< foreach collection= "rids" item= "rid" separator= "," >
( #{ uid} , #{ rid} )
< / foreach>
< / insert>
< / mapper>
4. 主要功能测试
菜单
五、 使用redis存储验证码
String remoteAddr = request. getRemoteAddr ( ) ;
ValueOperations < String , Object > valueOperations = redisTemplate. opsForValue ( ) ;
valueOperations. set ( remoteAddr+ "captcha" , code, 60 , TimeUnit . SECONDS) ;
ValueOperations < String , Object > valueOperations = redisTemplate. opsForValue ( ) ;
String remoteAddr = request. getRemoteAddr ( ) ;
String captcha = ( String ) valueOperations. get ( remoteAddr + "captcha" ) ;
六、使用分页查询和添加操作
一些小伙伴私信我,没做分页,没做添加,我这里提供整合分页的方法,大家自己写个controller接口,照猫画虎吧
1. 分页
集成PageHelper插件
引入依赖
< dependency>
< groupId> com. github. pagehelper< / groupId>
< artifactId> pagehelper- spring- boot- starter< / artifactId>
< version> 1.4 .1 < / version>
< / dependency>
yaml配置文件配置插件
pagehelper :
helperDialect : mysql
reasonable : true
supportMethodsArguments : true
params : count=countSql
接下来一点很重要,保证你的实体类中,没有直接实例化的容器,否则当容器没有内容时,会报空指针异常
下面roles是一个集合,并在实例化时,进行集合注入,此时一旦roles为null,pageHeader依然执行方法,进行对集合的操作,此时就会报空指针异常。所以我们加if判断 另外,这个玩意遍历拼接的是role的name属性,所以最好对name也做判断
整合完成以后,我们就可以使用了,接下来提供新接口使用分页插件实现分页
@ApiOperation ( "分页获取所有操作员以及它拥有的角色和权限,传递查询条件时,不要携带security的字段,只携带DdUser对象本身的字段,JSON" )
@PostMapping ( "/page/{current}/{size}" )
public Result getAllUser ( @RequestBody DdUser ddUser, @PathVariable Integer current, @PathVariable Integer size) {
Page < Object > objects = PageHelper . startPage ( current, size) ;
List < DdUser > list = ddUserService. getAllUser ( ddUser) ;
if ( list. isEmpty ( ) ) {
return Result . error ( ) . message ( "没有获取的用户信息!!!" ) ;
}
System . out. println ( "当前页码(从1开始的)" + objects. getPageNum ( ) ) ;
System . out. println ( "总页数" + objects. getPages ( ) ) ;
System . out. println ( "共有多少条数据" + objects. getTotal ( ) ) ;
return Result . ok ( )
. data ( "allUserListPage" , objects)
. data ( "pageNum" , objects. getPageNum ( ) )
. data ( "pages" , objects. getPages ( ) )
. data ( "total" , objects. getTotal ( ) ) ;
}
现在,只有你想分页,就按照上面的步骤,提供新接口,然后在要执行sql的地方上面,添加代码PageHelper.startPage(current,size);即可
注意,它只对此行代码下面的第一条执行的sql进行分页
2. 添加操作
我们要生成随机id,可以借助一个工具hutool-all,我们引入maven依赖
< dependency>
< groupId> cn. hutool< / groupId>
< artifactId> hutool- all< / artifactId>
< version> 5.7 .20 < / version>
< / dependency>
为了方便我将,我将数据库和java实体类的id都换成了字符串型,你可能需要改一些地方,mapper.xml文件中也可能需要修改 controller,使用工具类生成随机id,注意密码需要加密
@ApiOperation ( "添加用户" )
@PostMapping ( "/add" )
public Result addUser ( @RequestBody DdUser ddUser) {
Snowflake snowflake = IdUtil . getSnowflake ( 0 , 0 ) ;
ddUser. setId ( snowflake. nextIdStr ( ) ) ;
String encode = passwordEncoder. encode ( ddUser. getPassword ( ) ) ;
ddUser. setPassword ( encode) ;
if ( ddUserService. save ( ddUser) ) {
return Result . ok ( ) . message ( "添加成功" ) ;
}
return Result . error ( ) . message ( "添加失败" ) ;
}
测试
七、解决高版本循环依赖问题
实现了权限管理系统后,出现高版本循环依赖的问题,如下
当spring boot版本为2.6.X以上时,出现问题
SecurityConfig配置类中,注入了private DdUserService ddUserService; DdUserServiceImpl又注入了UserDetailsService
SecurityConfig注释private DdUserService ddUserService;,然后使用DdUserMapper
循环依赖解决,但是又出现了最近经常出现的问题,springboot2.6.x与swagger2不兼容的问题,这个和咱代码就没关系了,解决方案如下,修改配置,或者升级swagger或者降低springboot版本到2.5.x
解决循环依赖,新问题如下,表面上是swagger2找不到东西了,其实是ant_path_matcher参数的问题 配置yaml参数
spring :
main :
allow-circular-references : true
mvc :
pathmatch :
matching-strategy : ant_path_matcher
问题解决,项目跑起来了,但是此时swagger不能用了,显示跨域问题 修改跨域配置
@Override
public void addCorsMappings ( CorsRegistry registry) {
registry. addMapping ( "/**" )
. allowedOriginPatterns ( "*" )
. allowedMethods ( "GET" , "HEAD" , "POST" , "PUT" , "DELETE" , "OPTIONS" )
. allowCredentials ( true )
. maxAge ( 3600 )
. allowedHeaders ( "*" ) ;
}
八、前端对接