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。

🔗 更多信息


相关推荐
zquwei2 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
道一云黑板报6 小时前
Flink集群批作业实践:七析BI批作业执行
大数据·分布式·数据分析·flink·kubernetes
DT辰白6 小时前
基于Redis的网关鉴权方案与性能优化
数据库·redis·缓存
木子七7 小时前
Redis-十大数据类型
redis
飞来又飞去7 小时前
kafka sasl和acl之间的关系
分布式·kafka
MZWeiei8 小时前
Zookeeper的监听机制
分布式·zookeeper
莹雨潇潇8 小时前
Hadoop完全分布式环境部署
大数据·hadoop·分布式
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭9 小时前
聊聊volatile的实现原理?
java·jvm·redis
浩哲Zhe9 小时前
RabbitMQ
java·分布式·rabbitmq
BothSavage9 小时前
Knife4j在Gateway下的URI优化以及热刷新
windows·gateway