
轻松高效的现代化开发体验
Sun Frame 是我个人开源的一款基于 SpringBoot 的轻量级框架,专为中小型企业设计。它提供了一种快速、简单且易于扩展的开发方式。

假设数据库更新成功,缓存更新失败,在缓存失效和过期的时候,读取到的都是老数据缓存。
缓存更新成功了,数据库更新失败,是不是读取的缓存的都是错误的。
以上两种,全都不推荐。
有一定的使用量。即使数据库更新失败。缓存也可以会刷。
存在的问题是什么?
高并发情况下!!
比如说有两个线程,一个是 A 线程,一个是 B 线程。
A 线程把数据删了,正在更新数据库,这个时候 B 线程来了,发现缓存没了,又查数据,又放入缓存。缓存里面存的就一直是老数据了。
延迟双删。更新完数据库之后,再删一次。
删除失败的缓存,作为消息打入 mq,mq 消费者进行监听,再次进行重试刷缓存。
监听数据库的变化,做一个公共服务,专门来对接缓存刷新。优点业务解耦,业务太多冗余代码复杂度。
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.12.7version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.12.7version>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.8.6version>
dependency>
package com.sunxiansheng.auth.domain.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Description: 原生 redis 的 template 的序列化器会产生乱码问题,重写改为 jackson
* @Author sun
* @Create 2024/6/5 14:16
* @Version 1.0
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
return redisTemplate;
}
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jsonRedisSerializer.setObjectMapper(objectMapper);
return jsonRedisSerializer;
}
}
package com.sunxiansheng.auth.domain.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Description: RedisUtil工具类
* @Author sun
* @Create 2024/6/5 14:17
* @Version 1.0
*/
@Component
@Slf4j
public class RedisUtil {
@Resource
private RedisTemplate redisTemplate;
private static final String CACHE_KEY_SEPARATOR = ".";
/**
* 构建缓存key
*/
public String buildKey(String... strObjs) {
return Stream.of(strObjs).collect(Collectors.joining(CACHE_KEY_SEPARATOR));
}
/**
* 是否存在key
*/
public boolean exist(String key) {
return redisTemplate.hasKey(key);
}
/**
* 删除key
*/
public boolean del(String key) {
return redisTemplate.delete(key);
}
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public boolean setNx(String key, String value, Long time, TimeUnit timeUnit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
}
public String get(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
public Boolean zAdd(String key, String value, Long score) {
return redisTemplate.opsForZSet().add(key, value, Double.valueOf(String.valueOf(score)));
}
public Long countZset(String key) {
return redisTemplate.opsForZSet().size(key);
}
public Set<String> rangeZset(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
public Long removeZset(String key, Object value) {
return redisTemplate.opsForZSet().remove(key, value);
}
public void removeZsetList(String key, Set<String> value) {
value.stream().forEach((val) -> redisTemplate.opsForZSet().remove(key, val));
}
public Double score(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
public Set<String> rangeByScore(String key, long start, long end) {
return redisTemplate.opsForZSet().rangeByScore(key, Double.valueOf(String.valueOf(start)), Double.valueOf(String.valueOf(end)));
}
public Object addScore(String key, Object obj, double score) {
return redisTemplate.opsForZSet().incrementScore(key, obj, score);
}
public Object rank(String key, Object obj) {
return redisTemplate.opsForZSet().rank(key, obj);
}
}
// 要把当前用户的角色和权限都放到redis里
// 1、存储角色
// 构建一个角色的key
String roleKey = redisUtil.buildKey(authRolePrefix, authUser.getUserName());
// 构建一个角色列表作为value
List<AuthRole> roleList = new ArrayList<>();
// 向角色列表中添加角色
roleList.add(authRole);
// 将角色列表序列化并放到redis中
redisUtil.set(roleKey, new Gson().toJson(roleList));
// 2、存储权限
// 查询当前用户拥有的权限
// 1.注册的时候,用户只有一个角色,先根据这个角色id去角色权限关联表中查询多条关联的记录
AuthRolePermission authRolePermission = new AuthRolePermission();
// 设置逻辑删除
authRolePermission.setIsDeleted(IsDeleteFlagEnum.UN_DELETED.getCode());
// 设置角色id
authRolePermission.setRoleId(roleId);
// 查询出一个角色权限关联的列表
List<AuthRolePermission> authRolePermissionList = authRolePermissionService.queryByCondition(authRolePermission);
// 2.根据查询出来的列表,得到所有的权限id
List<Long> permissionIdList = authRolePermissionList.stream().map(AuthRolePermission::getPermissionId).collect(Collectors.toList());
// 根据权限id,查询所有的权限,就是根据ids批量查询
List<AuthPermission> permissionList = authPermissionService.queryByIds(permissionIdList);
// 将权限列表序列化并放到redis中
String permissionKey = redisUtil.buildKey(authPermissionPrefix, authUser.getUserName());
redisUtil.set(permissionKey, new Gson().toJson(permissionList));

/**
* 根据角色id查询角色权限关联表
* @param authRolePermission
* @return
*/
List<AuthRolePermission> queryByCondition(AuthRolePermission authRolePermission);
/**
* 根据角色id查询角色权限关联表
* @param authRolePermission
* @return
*/
@Override
public List<AuthRolePermission> queryByCondition(AuthRolePermission authRolePermission) {
return this.authRolePermissionDao.queryAllByLimit(authRolePermission);
}
/**
* 通过ids查询数据
* @param ids
* @return
*/
public List<AuthPermission> queryByIds(List<Long> ids);
/**
* 通过ids查询数据
* @param ids
* @return
*/
public List<AuthPermission> queryByIds(List<Long> ids) {
return authPermissionDao.queryByIds(ids);
}
/**
* 通过ID批量查询
*
* @param ids
* @return
*/
List<AuthPermission> queryByIds(@Param("ids") List<Long> ids);
<select id="queryByIds" resultMap="AuthPermissionMap">
select id, name, parent_id, type, menu_url, status, `show`, icon, permission_key, created_by, created_time,
update_by, update_time, is_deleted
from auth_permission
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
foreach>
select>


package com.sunxiansheng.club.gateway.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* (AuthPermission)实体类
*
* @author makejava
* @since 2024-06-06 17:16:58
*/
@Data
public class AuthPermission implements Serializable {
private Long id;
/**
* 权限名称
*/
private String name;
/**
* 父id
*/
private Long parentId;
/**
* 权限类型 0菜单 1操作
*/
private Integer type;
/**
* 菜单路由
*/
private String menuUrl;
/**
* 状态 0启用 1禁用
*/
private Integer status;
/**
* 展示状态 0展示 1隐藏
*/
private Integer show;
/**
* 图标
*/
private String icon;
/**
* 权限唯一标识
*/
private String permissionKey;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否被删除 0为删除 1已删除
*/
private Integer isDeleted;
}
package com.sunxiansheng.club.gateway.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* (AuthRole)实体类
*
* @author makejava
* @since 2024-06-06 14:30:38
*/
@Data
public class AuthRole implements Serializable {
private Long id;
/**
* 角色名称
*/
private String roleName;
/**
* 角色唯一标识
*/
private String roleKey;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否被删除 0未删除 1已删除
*/
private Integer isDeleted;
}
/**
* 根据loginId和前缀获取权限/角色列表
* @param loginId
* @param prefix
* @return
*/
private List<String> getAuth(String loginId, String prefix) {
// 得到该用户在redis中存储的key
String authKey = redisUtil.buildKey(prefix, loginId.toString());
// 从redis中获取列表
String authValue = redisUtil.get(authKey);
// 判空
if (StringUtils.isBlank(authValue)) {
return Collections.emptyList();
}
List<String> authList = new LinkedList<>();
// 根据前缀来决定将内容反序列化为什么形式
if (authRolePrefix.equals(prefix)) {
// 如果是角色列表的前缀,就反序列化为角色类型的
List<AuthRole> authRoleList = new Gson().fromJson(authValue, new TypeToken<List<AuthRole>>() {
}.getType());
// 得到roleKey的列表,放到authList中
authList = authRoleList.stream().map(AuthRole::getRoleKey).collect(Collectors.toList());
} else if (authPermissionPrefix.equals(prefix)) {
// 如果是权限列表,就反序列化为权限类型的
List<AuthPermission> authPermissionList = new Gson().fromJson(authValue, new TypeToken<List<AuthPermission>>() {
}.getType());
// 得到permissionKey,放到authList中
authList = authPermissionList.stream().map(AuthPermission::getPermissionKey).collect(Collectors.toList());
}
return authList;
}




