欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 网关与auth微服务缓存打通

网关与auth微服务缓存打通

2024/10/25 6:28:35 来源:https://blog.csdn.net/m0_64637029/article/details/140950426  浏览:    关键词:网关与auth微服务缓存打通

文章目录

  • 🌞 Sun Frame:SpringBoot 的轻量级开发框架(个人开源项目推荐)
    • 🌟 亮点功能
    • 📦 spring cloud模块概览
      • 常用工具
    • 🔗 更多信息
    • 1.缓存一致性问题
        • 1、更新了数据库,再更新缓存
        • 2、更新缓存,更新数据库
        • 3、先删除缓存,再更新数据库
        • 扩展思路
          • 1、消息队列补偿
          • 2、canal
    • 2.auth微服务在用户注册时,将当前用户的角色和权限都放到redis里
        • 1.sun-club-auth-domain
          • 1.pom.xml 引入依赖
          • 2.RedisConfig.java
          • 3.RedisUtil.java
          • 4.AuthUserDomainServiceImpl.java register方法新增逻辑
        • 2.sun-club-auth-infra
          • 1.AuthRolePermissionService.java
          • 2.AuthRolePermissionServiceImpl.java
          • 3.AuthPermissionService.java
          • 4.AuthPermissionServiceImpl.java
          • 5.AuthPermissionDao.java
          • 6.AuthPermissionDao.xml
        • 3.测试
    • 3.gateway鉴权时可以获取权限/角色列表
        • 1.sun-club-
          • 1.复制两个entity到这个模块
            • 1.AuthPermission.java
            • 2.AuthRole.java
          • 2.StpInterfaceImpl.java 根据loginId和前缀获取权限/角色列表
        • 3.sun-club-auth-application-controller
          • 在UserController.java可以设置用户登录时的token对应的loginId,这里设置成鸡翅
        • 3.测试
          • 1.首先登录,生成token和loginId(这里写死为鸡翅)
          • 2.然后携带token进行登录,后端就可以找到对应的loginId,在验证登录成功之后会进行鉴权
            • 1.在gateway的SaTokenConfigure.java可以配置鉴权的类型
            • 2.下面是分别两种方式,从redis中取出的角色列表和权限列表

🌞 Sun Frame:SpringBoot 的轻量级开发框架(个人开源项目推荐)

Sun Frame Banner

轻松高效的现代化开发体验

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

我们的开发文档记录了整个项目从0到1的任何细节,实属不易,请给我们一个Star!🌟
您的支持是我们持续改进的动力。

🌟 亮点功能

  • 组件化开发:灵活选择,简化流程。
  • 高性能:通过异步日志和 Redis 缓存提升性能。
  • 易扩展:支持多种数据库和消息队列。

📦 spring cloud模块概览

  • Nacos 服务:高效的服务注册与发现。
  • Feign 远程调用:简化服务间通信。
  • 强大网关:路由与限流。

常用工具

  • 日志管理:异步处理与链路追踪。
  • Redis 集成:支持分布式锁与缓存。
  • Swagger 文档:便捷的 API 入口。
  • 测试支持:SpringBoot-Test 集成。
  • EasyCode:自定义EasyCode模板引擎,一键生成CRUD。

🔗 更多信息

  • 开源地址:Gitee Sun Frame
  • 详细文档:语雀文档
    在这里插入图片描述

1.缓存一致性问题

1、更新了数据库,再更新缓存

假设数据库更新成功,缓存更新失败,在缓存失效和过期的时候,读取到的都是老数据缓存。

2、更新缓存,更新数据库

缓存更新成功了,数据库更新失败,是不是读取的缓存的都是错误的。

以上两种,全都不推荐。

3、先删除缓存,再更新数据库

有一定的使用量。即使数据库更新失败。缓存也可以会刷。

存在的问题是什么?

高并发情况下!!

比如说有两个线程,一个是 A 线程,一个是 B 线程。

A 线程把数据删了,正在更新数据库,这个时候 B 线程来了,发现缓存没了,又查数据,又放入缓存。缓存里面存的就一直是老数据了。

延迟双删。更新完数据库之后,再删一次。

扩展思路
1、消息队列补偿

删除失败的缓存,作为消息打入 mq,mq 消费者进行监听,再次进行重试刷缓存。

2、canal

监听数据库的变化,做一个公共服务,专门来对接缓存刷新。优点业务解耦,业务太多冗余代码复杂度。

2.auth微服务在用户注册时,将当前用户的角色和权限都放到redis里

1.sun-club-auth-domain
1.pom.xml 引入依赖
        <!-- 序列化 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.12.7</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.7</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version></dependency>
2.RedisConfig.java
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 {@Beanpublic 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;}}
3.RedisUtil.java
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 {@Resourceprivate 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);}}
4.AuthUserDomainServiceImpl.java register方法新增逻辑
// 要把当前用户的角色和权限都放到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));

image-20240607164025000

2.sun-club-auth-infra
1.AuthRolePermissionService.java
/*** 根据角色id查询角色权限关联表* @param authRolePermission* @return*/
List<AuthRolePermission> queryByCondition(AuthRolePermission authRolePermission);
2.AuthRolePermissionServiceImpl.java
/*** 根据角色id查询角色权限关联表* @param authRolePermission* @return*/
@Override
public List<AuthRolePermission> queryByCondition(AuthRolePermission authRolePermission) {return this.authRolePermissionDao.queryAllByLimit(authRolePermission);
}
3.AuthPermissionService.java
/*** 通过ids查询数据* @param ids* @return*/
public List<AuthPermission> queryByIds(List<Long> ids);
4.AuthPermissionServiceImpl.java
/*** 通过ids查询数据* @param ids* @return*/
public List<AuthPermission> queryByIds(List<Long> ids) {return authPermissionDao.queryByIds(ids);
}
5.AuthPermissionDao.java
/*** 通过ID批量查询** @param ids* @return*/
List<AuthPermission> queryByIds(@Param("ids") List<Long> ids);
6.AuthPermissionDao.xml
<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_deletedfrom auth_permissionwhere id in<foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach>
</select>
3.测试

image-20240607164355210

image-20240607164348873

3.gateway鉴权时可以获取权限/角色列表

1.sun-club-
1.复制两个entity到这个模块
1.AuthPermission.java
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;}
2.AuthRole.java
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;}
2.StpInterfaceImpl.java 根据loginId和前缀获取权限/角色列表
/*** 根据loginId和前缀获取权限/角色列表* @param loginId* @param prefix* @return*/
private List<String> getAuth(String loginId, String prefix) {// 得到该用户在redis中存储的keyString 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;
}
3.sun-club-auth-application-controller
在UserController.java可以设置用户登录时的token对应的loginId,这里设置成鸡翅

image-20240607171955303

3.测试
1.首先登录,生成token和loginId(这里写死为鸡翅)

image-20240607172214503

2.然后携带token进行登录,后端就可以找到对应的loginId,在验证登录成功之后会进行鉴权
1.在gateway的SaTokenConfigure.java可以配置鉴权的类型

image-20240607172332923

2.下面是分别两种方式,从redis中取出的角色列表和权限列表

image-20240607171208574

image-20240607171451314

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com