feat: Spring Boot 整合 Spring Cache 和 Redis 缓存

This commit is contained in:
haoxr
2023-12-04 21:39:12 +08:00
parent 0fa99a61ae
commit e28b3e9490
12 changed files with 176 additions and 35 deletions

View File

@@ -90,6 +90,11 @@
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aop</artifactId>

View File

@@ -0,0 +1,71 @@
package com.youlai.system.config;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* Redis 缓存配置
*
* @author haoxr
* @since 2023/12/4
*/
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
public class RedisCacheConfig {
/**
* 自定义 RedisCacheManager
* <p>
* 修改 Redis 序列化方式,默认 JdkSerializationRedisSerializer
*
* @param redisConnectionFactory {@link RedisConnectionFactory}
* @param cacheProperties {@link CacheProperties}
* @return {@link RedisCacheManager}
*/
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, CacheProperties cacheProperties){
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration(cacheProperties))
.build();
}
/**
* 自定义 RedisCacheConfiguration
*
* @param cacheProperties {@link CacheProperties}
* @return {@link RedisCacheConfiguration}
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
config = config.computePrefixWith(name -> name + ":");//覆盖默认key双冒号 CacheKeyPrefix#prefixed
return config;
}
}

View File

@@ -19,6 +19,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
@Tag(name = "03.角色接口") @Tag(name = "03.角色接口")
@@ -29,7 +30,7 @@ public class SysRoleController {
private final SysRoleService roleService; private final SysRoleService roleService;
@Operation(summary = "角色分页列表" ) @Operation(summary = "角色分页列表")
@GetMapping("/page") @GetMapping("/page")
public PageResult<RolePageVO> getRolePage( public PageResult<RolePageVO> getRolePage(
@ParameterObject RolePageQuery queryParams @ParameterObject RolePageQuery queryParams
@@ -57,7 +58,7 @@ public class SysRoleController {
@Operation(summary = "角色表单数据") @Operation(summary = "角色表单数据")
@GetMapping("/{roleId}/form") @GetMapping("/{roleId}/form")
public Result<RoleForm> getRoleForm( public Result<RoleForm> getRoleForm(
@Parameter(description ="角色ID") @PathVariable Long roleId @Parameter(description = "角色ID") @PathVariable Long roleId
) { ) {
RoleForm roleForm = roleService.getRoleForm(roleId); RoleForm roleForm = roleService.getRoleForm(roleId);
return Result.success(roleForm); return Result.success(roleForm);
@@ -75,7 +76,7 @@ public class SysRoleController {
@DeleteMapping("/{ids}") @DeleteMapping("/{ids}")
@PreAuthorize("@ss.hasPerm('sys:role:delete')") @PreAuthorize("@ss.hasPerm('sys:role:delete')")
public Result deleteRoles( public Result deleteRoles(
@Parameter(description ="删除角色,多个以英文逗号(,)分割") @PathVariable String ids @Parameter(description = "删除角色,多个以英文逗号(,)分割") @PathVariable String ids
) { ) {
boolean result = roleService.deleteRoles(ids); boolean result = roleService.deleteRoles(ids);
return Result.judge(result); return Result.judge(result);
@@ -84,8 +85,8 @@ public class SysRoleController {
@Operation(summary = "修改角色状态") @Operation(summary = "修改角色状态")
@PutMapping(value = "/{roleId}/status") @PutMapping(value = "/{roleId}/status")
public Result updateRoleStatus( public Result updateRoleStatus(
@Parameter(description ="角色ID") @PathVariable Long roleId, @Parameter(description = "角色ID") @PathVariable Long roleId,
@Parameter(description ="状态(1:启用;0:禁用)") @RequestParam Integer status @Parameter(description = "状态(1:启用;0:禁用)") @RequestParam Integer status
) { ) {
boolean result = roleService.updateRoleStatus(roleId, status); boolean result = roleService.updateRoleStatus(roleId, status);
return Result.judge(result); return Result.judge(result);
@@ -94,19 +95,19 @@ public class SysRoleController {
@Operation(summary = "获取角色的菜单ID集合") @Operation(summary = "获取角色的菜单ID集合")
@GetMapping("/{roleId}/menuIds") @GetMapping("/{roleId}/menuIds")
public Result<List<Long>> getRoleMenuIds( public Result<List<Long>> getRoleMenuIds(
@Parameter(description ="角色ID") @PathVariable Long roleId @Parameter(description = "角色ID") @PathVariable Long roleId
) { ) {
List<Long> menuIds = roleService.getRoleMenuIds(roleId); List<Long> menuIds = roleService.getRoleMenuIds(roleId);
return Result.success(menuIds); return Result.success(menuIds);
} }
@Operation(summary = "分配菜单权限给角色") @Operation(summary = "分配菜单(包括按钮权限)给角色")
@PutMapping("/{roleId}/menus") @PutMapping("/{roleId}/menus")
public Result updateRoleMenus( public Result assignMenusToRole(
@PathVariable Long roleId, @PathVariable Long roleId,
@RequestBody List<Long> menuIds @RequestBody List<Long> menuIds
) { ) {
boolean result = roleService.updateRoleMenus(roleId,menuIds); boolean result = roleService.assignMenusToRole(roleId, menuIds);
return Result.judge(result); return Result.judge(result);
} }
} }

View File

@@ -5,7 +5,7 @@ import com.youlai.system.model.entity.SysUserRole;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
/** /**
* 用户角色持久 * 用户角色访问
* *
* @author haoxr * @author haoxr
* @since 2022/1/15 * @since 2022/1/15
@@ -13,4 +13,10 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> { public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
/**
* 统计角色下绑定的用户数量
*
* @param roleId 角色ID
*/
int countUsersForRole(Long roleId);
} }

View File

@@ -85,7 +85,7 @@ public interface SysRoleService extends IService<SysRole> {
* @param menuIds * @param menuIds
* @return * @return
*/ */
boolean updateRoleMenus(Long roleId, List<Long> menuIds); boolean assignMenusToRole(Long roleId, List<Long> menuIds);
/** /**
* 获取最大范围的数据权限 * 获取最大范围的数据权限

View File

@@ -16,4 +16,12 @@ public interface SysUserRoleService extends IService<SysUserRole> {
* @return * @return
*/ */
boolean saveUserRoles(Long userId, List<Long> roleIds); boolean saveUserRoles(Long userId, List<Long> roleIds);
/**
* 判断角色是否存在绑定的用户
*
* @param roleId 角色ID
* @return true已分配 false未分配
*/
boolean isRoleAssignedToUser(Long roleId);
} }

View File

@@ -10,10 +10,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.system.common.constant.SystemConstants; import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.common.model.Option; import com.youlai.system.common.model.Option;
import com.youlai.system.converter.RoleConverter; import com.youlai.system.converter.RoleConverter;
import com.youlai.system.core.security.service.PermissionService;
import com.youlai.system.mapper.SysRoleMapper; import com.youlai.system.mapper.SysRoleMapper;
import com.youlai.system.model.entity.SysRole; import com.youlai.system.model.entity.SysRole;
import com.youlai.system.model.entity.SysRoleMenu; import com.youlai.system.model.entity.SysRoleMenu;
import com.youlai.system.model.entity.SysUserRole;
import com.youlai.system.model.form.RoleForm; import com.youlai.system.model.form.RoleForm;
import com.youlai.system.model.query.RolePageQuery; import com.youlai.system.model.query.RolePageQuery;
import com.youlai.system.model.vo.RolePageVO; import com.youlai.system.model.vo.RolePageVO;
@@ -39,9 +39,10 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService { public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
private final SysRoleMenuService sysRoleMenuService; private final SysRoleMenuService roleMenuService;
private final SysUserRoleService sysUserRoleService; private final SysUserRoleService userRoleService;
private final RoleConverter roleConverter; private final RoleConverter roleConverter;
private final PermissionService permissionService;
/** /**
* 角色分页列表 * 角色分页列表
@@ -136,10 +137,9 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
*/ */
@Override @Override
public boolean updateRoleStatus(Long roleId, Integer status) { public boolean updateRoleStatus(Long roleId, Integer status) {
boolean result = this.update(new LambdaUpdateWrapper<SysRole>() return this.update(new LambdaUpdateWrapper<SysRole>()
.eq(SysRole::getId, roleId) .eq(SysRole::getId, roleId)
.set(SysRole::getStatus, status)); .set(SysRole::getStatus, status));
return result;
} }
/** /**
@@ -153,15 +153,23 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
Assert.isTrue(StrUtil.isNotBlank(ids), "删除的角色ID不能为空"); Assert.isTrue(StrUtil.isNotBlank(ids), "删除的角色ID不能为空");
List<Long> roleIds = Arrays.stream(ids.split(",")) List<Long> roleIds = Arrays.stream(ids.split(","))
.map(Long::parseLong) .map(Long::parseLong)
.collect(Collectors.toList()); .toList();
roleIds.forEach(id -> { for (Long roleId : roleIds) {
long count = sysUserRoleService.count(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getRoleId, id)); SysRole role = this.getById(roleId);
Assert.isTrue(count <= 0, "角色已分配用户,无法删除"); Assert.isTrue(role != null, "角色不存在");
sysRoleMenuService.remove(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, id));
});
return this.removeByIds(roleIds); // 判断角色是否被用户关联
boolean isRoleAssigned = userRoleService.isRoleAssignedToUser(roleId);
Assert.isTrue(!isRoleAssigned, "角色【{}】已分配用户,请先解除关联后删除", role.getName());
boolean deleteResult = this.removeById(roleId);
if(deleteResult) {
// 删除成功,刷新权限缓存
permissionService.refreshPermissionCache(role.getCode());
}
}
return true;
} }
/** /**
@@ -172,7 +180,7 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
*/ */
@Override @Override
public List<Long> getRoleMenuIds(Long roleId) { public List<Long> getRoleMenuIds(Long roleId) {
return sysRoleMenuService.listMenuIdsByRoleId(roleId); return roleMenuService.listMenuIdsByRoleId(roleId);
} }
/** /**
@@ -185,15 +193,16 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
@Override @Override
@Transactional @Transactional
@CacheEvict(cacheNames = "system", key = "'routes'") @CacheEvict(cacheNames = "system", key = "'routes'")
public boolean updateRoleMenus(Long roleId, List<Long> menuIds) { public boolean assignMenusToRole(Long roleId, List<Long> menuIds) {
// 删除角色菜单 // 删除角色菜单
sysRoleMenuService.remove(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId)); roleMenuService.remove(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
// 新增角色菜单 // 新增角色菜单
if (CollectionUtil.isNotEmpty(menuIds)) { if (CollectionUtil.isNotEmpty(menuIds)) {
List<SysRoleMenu> roleMenus = menuIds.stream() List<SysRoleMenu> roleMenus = menuIds
.stream()
.map(menuId -> new SysRoleMenu(roleId, menuId)) .map(menuId -> new SysRoleMenu(roleId, menuId))
.collect(Collectors.toList()); .toList();
sysRoleMenuService.saveBatch(roleMenus); roleMenuService.saveBatch(roleMenus);
} }
return true; return true;
} }

View File

@@ -32,7 +32,7 @@ public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUs
List<Long> userRoleIds = this.list(new LambdaQueryWrapper<SysUserRole>() List<Long> userRoleIds = this.list(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getUserId, userId)) .eq(SysUserRole::getUserId, userId))
.stream() .stream()
.map(item -> item.getRoleId()) .map(SysUserRole::getRoleId)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 新增用户角色 // 新增用户角色
@@ -67,4 +67,16 @@ public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUs
return true; return true;
} }
/**
* 判断角色是否存在绑定的用户
*
* @param roleId 角色ID
* @return true已分配 false未分配
*/
@Override
public boolean isRoleAssignedToUser(Long roleId) {
int count = this.baseMapper.countUsersForRole(roleId);
return count > 0;
}
} }

View File

@@ -30,6 +30,14 @@ spring:
max-idle: 8 max-idle: 8
# 连接池中的最小空闲连接 默认0 # 连接池中的最小空闲连接 默认0
min-idle: 0 min-idle: 0
cache:
# 缓存类型 redis、none(不使用缓存)
type: redis
# 缓存时间(单位ms)
redis:
time-to-live: 3600000
# 缓存null值防止缓存穿透
cache-null-values: true
mybatis-plus: mybatis-plus:
global-config: global-config:
db-config: db-config:

View File

@@ -30,6 +30,14 @@ spring:
max-idle: 8 max-idle: 8
# 连接池中的最小空闲连接 默认0 # 连接池中的最小空闲连接 默认0
min-idle: 0 min-idle: 0
cache:
# 缓存类型 redis、none(不使用缓存)
type: redis
# 缓存时间(单位ms)
redis:
time-to-live: 3600000
# 缓存null值防止缓存穿透
cache-null-values: true
mybatis-plus: mybatis-plus:
global-config: global-config:
db-config: db-config:

View File

@@ -30,7 +30,7 @@
t3.perm t3.perm
FROM FROM
`sys_role_menu` t1 `sys_role_menu` t1
INNER JOIN sys_role t2 ON t1.role_id = t2.id INNER JOIN sys_role t2 ON t1.role_id = t2.id AND t2.deleted = 0
INNER JOIN sys_menu t3 ON t1.menu_id = t3.id INNER JOIN sys_menu t3 ON t1.menu_id = t3.id
WHERE WHERE
type = '${@com.youlai.system.common.enums.MenuTypeEnum@BUTTON.getValue()}' type = '${@com.youlai.system.common.enums.MenuTypeEnum@BUTTON.getValue()}'

View File

@@ -13,4 +13,17 @@
WHERE WHERE
user_id = #{userId} user_id = #{userId}
</select> </select>
<!-- 统计角色下绑定的用户数量 -->
<select id="countUsersForRole" resultType="java.lang.Integer">
SELECT
count(*)
FROM
sys_user_role t1
INNER JOIN sys_role t2 ON t1.role_id = t2.id AND t2.deleted = 0
INNER JOIN sys_user t3 ON t1.user_id = t3.id
AND t3.deleted = 0
WHERE
t1.role_id = #{roleId}
</select>
</mapper> </mapper>