GateWay基于Redis实现分布式会话+全局异常

文章目录

1.Redis集群连接

1.docker启动命令(不用启动,之前配置过)
sh 复制代码
docker run -d --name redis-node-1 --net=host --privileged=true \
  -v /app/redis/data:/data \
  -v /app/redis/redis.conf:/etc/redis/redis.conf \
  redis:6.0.8 \
  redis-server /etc/redis/redis.conf \
  --cluster-enabled yes \
  --appendonly yes \
  --dir /data \
  --port 6480 \
  --cluster-config-file /data/nodes.conf \
  --cluster-node-timeout 5000
2.常用命令
1.进入Redis容器
复制代码
docker exec -it redis-node-5 /bin/bash
2.登录redis
sh 复制代码
redis-cli -c -p 6480 --askpass
3.查看集群信息
sh 复制代码
CLUSTER INFO
4.查看集群节点
sh 复制代码
CLUSTER NODES
3.使用Redis桌面工具连接

2.网关模块sa-token集成redis集群

1.引入依赖
xml 复制代码
<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-jackson</artifactId>
    <version>1.37.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
2.application.yml 在有sa-token的基础上集成Redis
  • 注意:database需要与集群设置的数据库一致
yaml 复制代码
  redis:
    password: guest # Redis服务器密码
    database: 0 # 需要与集群设置的数据库一致
    timeout: 10000ms # 连接超时时间是10000毫秒
    lettuce:
      pool:
        max-active: 8 # 最大活跃连接数,使用负值表示没有限制,最佳配置为核数*2
        max-wait: 10000ms # 最大等待时间,单位为毫秒,使用负值表示没有限制,这里设置为10秒
        max-idle: 200 # 最大空闲连接数
        min-idle: 5 # 最小空闲连接数
    cluster:
      nodes:
        - guest
        - guest
        - guest
        - guest
        - guest
        - guest
3.auth模块集成redis
1.sun-club-auth-application-controller引入依赖
xml 复制代码
<!-- Sa-Token集成redis -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-jackson</artifactId>
    <version>1.37.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.9.0</version>
</dependency>
2.application.yml
yaml 复制代码
  redis:
    password: guest # Redis服务器密码
    database: 0 # 需要与集群设置的数据库一致
    timeout: 10000ms # 连接超时时间是10000毫秒
    lettuce:
      pool:
        max-active: 8 # 最大活跃连接数,使用负值表示没有限制,最佳配置为核数*2
        max-wait: 10000ms # 最大等待时间,单位为毫秒,使用负值表示没有限制,这里设置为10秒
        max-idle: 200 # 最大空闲连接数
        min-idle: 5 # 最小空闲连接数
    cluster:
      nodes:
		guest
sa-token:
  # token 名称(同时也是 cookie 名称)
  token-name: satoken
  # token 有效期(单位:秒) 默认30天,-1 代表永久有效
  timeout: 2592000
  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
  active-timeout: -1
  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
  is-share: true
  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
  token-style: random-32
  # 是否输出操作日志
  is-log: true
  token-prefix: jichi
4.启动测试
1.登录
2.验证登录
5.自定义权限验证接口扩展
com.sunxiansheng.club.gateway.auth.StpInterfaceImpl 这样设置就表示请求的用户为admin具有user:add权限
java 复制代码
package com.sunxiansheng.club.gateway.auth;

import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义权限验证接口扩展
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回此loginId拥有的权限列表
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        List<String> permissionList = new ArrayList<>();
        permissionList.add("user:add");
        return permissionList;
    }

    /**
     * 返回此loginId拥有的角色列表
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        List<String> roleList = new ArrayList<>();
        roleList.add("admin");
        return roleList;
    }

}
6.权限认证的全局过滤器
1.com.sunxiansheng.club.gateway.auth.SaTokenConfigure
java 复制代码
package com.sunxiansheng.club.gateway.auth;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.UUID;

/**
 * 权限认证的全局过滤器
 */
@Configuration
public class SaTokenConfigure {

    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 给redis 中写入数据
                    SaManager.getSaTokenDao().set("name" + UUID.randomUUID(), "value", 100000);
                    System.out.println("-------- 前端访问path:" + SaHolder.getRequest().getRequestPath());
                    // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
                    SaRouter.match("/auth/**", "/auth/user/doLogin", r -> StpUtil.checkRole("admin"));
                    SaRouter.match("/oss/**", r -> StpUtil.checkLogin());
                    SaRouter.match("/subject/subject/add", r -> StpUtil.checkPermission("subject:add"));
                    SaRouter.match("/subject/**", r -> StpUtil.checkLogin());
                })
                ;
    }
}
2.测试
1.网关登录
2.网关验证登录

3.GateWay实现全局异常

1.目录结构
2.Result.java
java 复制代码
package com.sunxiansheng.club.gateway.entity;

import com.sunxiansheng.club.gateway.enums.ResultCodeEnum;
import lombok.Data;

/**
 * Description:
 * @Author sun
 * @Create 2024/5/24 9:48
 * @Version 1.0
 */
@Data
public class Result<T> {

    private Boolean success;

    private Integer code;

    private String message;

    private T data;

    /**
     * 成功返回结果
     * @return
     */
    public static Result ok() {
        Result result = new Result();
        result.setSuccess(true);
        result.setCode(ResultCodeEnum.SUCCESS.getCode());
        result.setMessage(ResultCodeEnum.SUCCESS.getDesc());
        return result;
    }

    /**
     * 成功返回结果,携带数据
     * @param data
     * @return
     * @param <T>
     */
    public static <T> Result ok(T data) {
        Result result = new Result();
        result.setSuccess(true);
        result.setCode(ResultCodeEnum.SUCCESS.getCode());
        result.setMessage(ResultCodeEnum.SUCCESS.getDesc());
        result.setData(data);
        return result;
    }

    /**
     * 失败返回结果
     * @return
     */
    public static Result fail() {
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(ResultCodeEnum.FAIL.getCode());
        result.setMessage(ResultCodeEnum.FAIL.getDesc());
        return result;
    }

    /**
     * 失败,携带数据
     * @param data
     * @return
     * @param <T>
     */
    public static <T> Result fail(T data) {
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(ResultCodeEnum.FAIL.getCode());
        result.setMessage(ResultCodeEnum.FAIL.getDesc());
        result.setData(data);
        return result;
    }

    /**
     * 扩展方法,可以返回code和message
     * @param code
     * @param message
     * @return
     */
    public static Result fail(Integer code, String message) {
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

}
3.ResultCodeEnum.java
java 复制代码
package com.sunxiansheng.club.gateway.enums;

import lombok.Getter;

/**
 * Description: 返回结果枚举
 * @Author sun
 * @Create 2024/5/24 9:53
 * @Version 1.0
 */
@Getter
public enum ResultCodeEnum {
    SUCCESS(200, "成功"),
    FAIL(500, "失败");

    public int code;
    public String desc;

    ResultCodeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    /**
     * 根据code获取枚举
     * @param code
     * @return
     */
    public static ResultCodeEnum getByCode(int code) {
        for (ResultCodeEnum value : values()) {
            if (value.code == code) {
                return value;
            }
        }
        return null;
    }
}
4.GateWayExceptionHandler.java gateway的全局异常处理器
java 复制代码
package com.sunxiansheng.club.gateway.exception;

import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sunxiansheng.club.gateway.entity.Result;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * Description: gateway的全局异常处理器
 * @Author sun
 * @Create 2024/6/5 13:18
 * @Version 1.0
 */
@Component
public class GateWayExceptionHandler implements ErrorWebExceptionHandler {

    // 组合一个ObjectMapper对象,将Result转化为bytes数组
    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     *
     * @param serverWebExchange 可以获取请求和响应
     * @param throwable 可以获取异常信息
     * @return
     */
    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
        // 获取请求和响应
        ServerHttpRequest request = serverWebExchange.getRequest();
        ServerHttpResponse response = serverWebExchange.getResponse();
        // 定义状态码和信息
        Integer code = 200;
        String message = "";
        // 如果抛出的异常是SaToken类型的,就返回401的状态码
        if (throwable instanceof SaTokenException) {
            code = 401;
            message = "用户无权限";
        } else {
            // 如果是其他的异常,就返回500的状态码
            code = 500;
            message = "服务器繁忙";
            throwable.printStackTrace();
        }
        // 构建Result
        Result result = Result.fail(code, message);
        // 返回json类型的数据
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        return response.writeWith(Mono.fromSupplier(() -> {
            // 构建一个数据工厂,一会返回
            DataBufferFactory dataBufferFactory = response.bufferFactory();
            byte[] bytes = null;
            // 将结果转化为byte数组
            try {
                bytes = objectMapper.writeValueAsBytes(result);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            // 返回
            return dataBufferFactory.wrap(bytes);
        }));
    }
}
5.测试

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

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

Sun Frame 是我个人开源的一款基于 SpringBoot 的轻量级框架,专为中小型企业设计。它提供了一种快速、简单且易于扩展的开发方式。
我们的开发文档记录了整个项目从0到1的任何细节,实属不易,请给我们一个Star!🌟
您的支持是我们持续改进的动力。

🌟 亮点功能

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

📦 spring cloud模块概览

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

常用工具

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

🔗 更多信息


相关推荐
摇滚侠8 小时前
Redis 秒杀功能 超卖问题 一人一单问题 分布式锁 精彩!精彩!
redis·分布式·bootstrap
笨鸟先飞的橘猫10 小时前
MMO游戏中的“跨服团队副本”匹配与状态同步系统
分布式·学习·游戏·lua·skynet
Emily呀13 小时前
【无标题】
redis
愈努力俞幸运14 小时前
function calling与mcp
android·数据库·redis
IronMurphy14 小时前
Redis拷打第一讲
数据库·redis·缓存
楠枬15 小时前
Redis 事务
数据库·redis·缓存
轻刀快马15 小时前
穿透 MQ 专栏 (五):【终局之战】MySQL 和 MQ 的世纪联姻:扒开“分布式事务”的遮羞布
数据库·分布式·消息队列
摇滚侠17 小时前
Redis 查询接口加缓存 缓存雪崩 缓存穿透 缓存击穿 精彩!精彩!
redis·缓存
Mr. zhihao17 小时前
[特殊字符] 从 Redis 缓存穿透到布隆过滤器,再到布谷鸟过滤器:一次穿透防护的进化之旅
数据库·redis·缓存
@小匠17 小时前
Redis 7 持久化机制
数据库·redis·缓存