satoken+ gateway网关统一鉴权 初版

news2024/10/3 0:31:08

一:感谢大佬

本博客内容 参考了satoken官网实现,satoken官网地址:
https://sa-token.cc/doc.html#/micro/gateway-auth

二:项目层级介绍

  1. jinyi-gateway 网关服务
  2. jinyi-user-service 用户服务
    2.1 jinyi-user-api
    2.2 jinyi-user-client
    2.3 jinyi-user-provider
  3. jinyi-common 通用服务,定义了一些统一返回类,全局常量(R等)
    项目
    层级关系截图:
    在这里插入图片描述
    在这里插入图片描述

三:项目具体介绍

3.1jinyi-gateway 网关服务

3.1.1 pom.xml
标签里只是指明了springboot springcloud springcloud-alibaba等版本信息和通用的像lombok等通用的类。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <parent>
        <artifactId>jinyi-up-project</artifactId>
        <groupId>com.jinyi.up</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jinyi-gateway</artifactId>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <alibaba.nacos.version>2.1.0</alibaba.nacos.version>
        <jinyi-up.version>1.0.0</jinyi-up.version>
        <sa-token.version>1.34.0</sa-token.version>
    </properties>

    <dependencies>
        <!--Spring Cloud & Alibaba-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <exclusions>
                <!--Sa-Token 权限认证(Reactor响应式)-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 注册中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- 配置中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--通用基础common-->
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-common-base</artifactId>
            <version>${jinyi-up.version}</version>
        </dependency>
        <!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:http://sa-token.dev33.cn/ -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        <!-- 提供Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-user-client</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-user-api</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

核心maven依赖

<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.34.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

config包中类:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
@RefreshScope
public class ExecutorConfig {
    @Value("${executor.corePoolSize:4}")
    private Integer corePoolSize;

    @Value("${executor.queueCapacity:1000}")
    private Integer queueCapacity;

    @Value("${executor.maxPoolSize:6}")
    private Integer maxPoolSize;

    @Value("${executor.keepAliveSeconds:30}")
    private Integer keepAliveSeconds;

    /**
     * 线程池参数定义
     * @return
     */
    @Bean
    public ThreadPoolTaskExecutor AsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("async-task-executor-");
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setMaxPoolSize(maxPoolSize);
        //线程大于coreSize,多余线程数超过30s则销毁
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 设置拒绝策略,调用当前线程执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.stream.Collectors;

/**
 * gateway是基于WebFlux的一个响应式组件,HttpMessageConverters不会像Spring Mvc一样自动注入,需要我们手动配置
 *
 */
@Configuration
public class HttpMessageConvertersConfigure {
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }
}

其中最核心的SaTokenConfigures类

import cn.dev33.satoken.config.SaTokenConfig;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.server.ServerWebExchange;

@Configuration
public class SaTokenConfigures {

    //需要放行的接口[白名单]
    private static final String[] PERMITURL = new String[]{
            "/user-api/jinyiUser/accountLogin"
    };

    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaServletFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")
                // 开放地址【白名单】
//                .addExclude(PERMITURL)
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
//                  SaRouter.match("/**", "/account-service/account/userLogin", r -> StpUtil.checkLogin());
                    SaRouter.match("/**")
                            .notMatch("/user-api/jinyiUser/accountLogin",
                                    "/**/swagger-resources/**", "/**/webjars/**",
                                    "/**/swagger-ui.html/**",
                                    "/**/doc.html/**", "/**/error",
                                    "/**/favicon.ico").check(r -> StpUtil.checkLogin());
                    // 权限认证 -- 不同模块, 校验不同权限
                      SaRouter.match("/jinyiUser/**", r -> StpUtil.checkRole("admin"));
//                    SaRouter.match("/user-api/jinyiUser/**", r -> StpUtil.checkRole("admin"));
//                    SaRouter.match("/jinyiUser/**", r -> StpUtil.checkPermission("system"));
//                    SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
//                    SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));

                    // ...
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    // 设置错误返回格式为JSON
                    ServerWebExchange exchange = SaReactorSyncHolder.getContext();
                    exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
                    //登录或者权限相关的resp json 都是在下面的代码里封装返回的
                    return SaResult.error(e.getMessage());
                })

                .setBeforeAuth(obj -> {
                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");

                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS).free(r -> System.out.println("--------OPTIONS预检请求,不做处理")).back();
                });
    }

    @Bean
    @Primary
    public SaTokenConfig getSaTokenConfigPrimary() {
        SaTokenConfig config = new SaTokenConfig();
        config.setTokenName("token");             // token名称 (同时也是cookie名称)
        config.setTimeout(30 * 24 * 60 * 60);       // token有效期,单位s 默认30天
        config.setActivityTimeout(-1);              // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
        config.setIsConcurrent(true);               // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
        config.setIsShare(true);                    // 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
        config.setTokenStyle("uuid");               // token风格
        config.setIsLog(false);                     // 是否输出操作日志
        return config;
    }
}

exception包GlobalException类

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.jinyi.up.common.base.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalException {
    
    @ExceptionHandler
    public R handlerException(Exception e) {
        e.printStackTrace();
        // 不同异常返回不同状态码
        R r = null;
        if (e instanceof NotLoginException) {	// 如果是未登录异常
            NotLoginException ee = (NotLoginException) e;
            log.error("登录异常:{}",e.getMessage());
            R.failed(ee.getMessage());
        }
        else if(e instanceof NotRoleException) {		// 如果是角色异常
            NotRoleException ee = (NotRoleException) e;
            R.failed("无此角色:" + ee.getRole());
        }
        else if(e instanceof NotPermissionException) {	// 如果是权限异常
            NotPermissionException ee = (NotPermissionException) e;
            R.failed("无此权限:" + ee.getMessage());
        }
//        else if(e instanceof DisableLoginException) {	// 如果是被封禁异常
//            R.failed("账号被封禁:" + e.getDisableTime() + "秒后解封");
//        }
        else {	// 普通异常, 输出:500 + 异常信息
            R.failed();
        }
        // 返回给前端
        return r;
    }

}

service包中

import cn.dev33.satoken.stp.StpInterface;
import com.google.common.collect.Lists;
import com.jinyi.up.common.base.result.R;
import com.jinyi.up.feignclient.UserProvideFeignClient;
import com.jinyi.up.user.vo.JinyiRoleVO;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Component
public class StpInterfaceImpl implements StpInterface {

    @Resource
    private UserProvideFeignClient userFeignClient;

    @Resource
    @Qualifier(value = "AsyncThreadPoolTaskExecutor")
    ThreadPoolTaskExecutor executor;

    /**
     * 返回指定账号id所拥有的权限码集合
     *
     * @param loginId   账号id
     * @param loginType 账号类型
     * @return 该账号id具有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表,没有设置权限表。此处为伪代码
        List<String> strs = new ArrayList<>();
        strs.add("system");
        return strs;
    }

    /**
     * 返回指定账号id所拥有的角色标识集合
     *
     * @param loginId   账号id
     * @param loginType 账号类型
     * @return 该账号id具有的角色标识集合
     */
    @SneakyThrows
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        List<String> result = Lists.newArrayList();
        CompletableFuture<R> AsyncResp = CompletableFuture.supplyAsync(() -> {
            return userFeignClient.getUserRoleByUserId((Long) loginId);
        }, executor);
        R<List<JinyiRoleVO>> resp = AsyncResp.get();
        if (R.isSuccess(resp) && !CollectionUtils.isEmpty(resp.getData())) {
            result = resp.getData().stream().map(JinyiRoleVO::getCode).collect(Collectors.toList());
        }
        return result;
    }

}

gateway 项目启动类

import com.jinyi.up.feignclient.UserProvideFeignClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
//@FeignClinet 和 @EnableFeignClients 不是同一个包。指定basePackages包扫描路径,指定basePackageClasses扫描类
@EnableFeignClients(basePackageClasses = {UserProvideFeignClient.class})
public class JinyiGateWayApplication {

    public static void main(String[] args) {
        SpringApplication.run(JinyiGateWayApplication.class,args);
    }
}

yml配置文件bootstrap.yml 和 application-dev.yml
bootstrap.yml 如下

server:
  port: 8070

spring:
  application:
    name: jinyi-gateway
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: dev
  cloud:
    nacos:
      # 注册中心
      discovery:
        server-addr: 127.0.0.1:8848
      # 配置中心
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        group: GATEWAY_GROUP   # 配置中心  配置中心是空的
#        shared-configs[0]:
#          data-id: jinyi-common.yaml
#          group: COMMON_GROUP
#          refresh: true
    #gateway的配置
    gateway:
#      discovery:
#        locator:
#          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
#          lower-case-service-id: true
      #路由规则
      routes:
        - id: jinyi-user-provider # 路由的id,没有规定规则但要求唯一,建议配合服务名
          uri: lb://jinyi-user-provider #uri: lb://路由项目的spring.application.name
          #断言 用于路由规则的匹配 路径相匹配的进行路由
          predicates:
            - Path=/user-api/**  #用户路由
          filters:
            - StripPrefix=1  #去掉路由url中的前缀 /jinyi-user
            # 关键在下面一句,值为true则开启认证,false则不开启
            # 这种配置方式和spring cloud gateway内置的GatewayFilterFactory一致
          #  - Authorize=false #自定义的过滤工厂
logging:
  level:
    spring.: DEBUG


# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: token
  # token有效期,单位秒,默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期),单位秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
  is-share: false
  # token风格
  token-style: uuid
  # 是否输出操作日志
  is-log: false
  # 是否从cookie中读取token
  is-read-cookie: false
  # 是否从head中读取token
  is-read-header: true
  # token前缀
#  token-prefix: Bearer

application-dev.yml如下

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 1
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password: 
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 3000ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0

3.2jinyi-user-service服务
3.2.1 jinyi-user-api项目
pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>jinyi-user-service</artifactId>
        <groupId>com.jinyi.up</groupId>
        <version>1.0.0</version>
    </parent>

    <groupId>com.jinyi.up</groupId>
    <artifactId>jinyi-user-api</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>jinyi-user-api</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
</project>

dto 和 vo目录下的类分别是

import io.swagger.annotations.ApiModel;
import lombok.Data;

@Data
@ApiModel(value = "用户账号登录")
public class UserAccountLoginDTO {

    private String account;

    private String password;
}

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel("用户角色VO")
public class JinyiRoleVO {

    @ApiModelProperty("主键id")
    private Long id;

    @ApiModelProperty("角色名称")
    private String name;

    @ApiModelProperty("角色编码")
    private String code;

    @ApiModelProperty("角色状态:1-正常;0-停用")
    private Boolean status;
}

jinyi-user-client项目:
pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jinyi-user-service</artifactId>
        <groupId>com.jinyi.up</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jinyi-user-client</artifactId>

    <name>jinyi-user-client</name>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <jinyi-up.version>1.0.0</jinyi-up.version>
    </properties>
    <dependencies>
        <!--引入base common-->
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-common-base</artifactId>
            <version>${jinyi-up.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <!--            <optional>true</optional>-->
        </dependency>
        <!-- openfeign依赖 1. http客户端选择okhttp 2. loadbalancer替换ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-user-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-user-api</artifactId>
            <version>1.0.0</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

</project>

核心feign类

import com.jinyi.up.common.base.result.R;
import com.jinyi.up.feignclient.fallback.UserProvideFeignFallback;
import com.jinyi.up.user.dto.UserAccountLoginDTO;
import com.jinyi.up.user.vo.JinyiRoleVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

/**
 * @FeignClient注解 value 指定的要代理的服务的spring:application:name配置信息
 * path 值的被代理的服务的统一路径
 */
@FeignClient(value = "jinyi-user-provider", path = "/jinyiUser",
        fallback = UserProvideFeignFallback.class) //实现UserProvideFeign服务降级
//        fallbackFactory = UserProvideFeignFallbackFactory.class)
public interface UserProvideFeignClient {

    @PostMapping("/accountLogin")
    R phoneLogin(@RequestBody UserAccountLoginDTO userPhoneLoginDTO);

    @GetMapping("/getUserRoleByUserId/{userId}")
    R<List<JinyiRoleVO>> getUserRoleByUserId(@PathVariable("userId") Long userId);
    
    @PostMapping("/userLogout")
    R userLogout();
}

jinyi-user-provider项目
pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>jinyi-user-service</artifactId>
        <groupId>com.jinyi.up</groupId>
        <version>1.0.0</version>
    </parent>
    <groupId>com.jinyi.up</groupId>
    <artifactId>jinyi-user-provider</artifactId>
    <version>1.0.0</version>

    <name>jinyi-user-provider</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <jinyi-up.version>1.0.0</jinyi-up.version>
        <sa-token.version>1.34.0</sa-token.version>
    </properties>

    <dependencies>
        <!-- 配置读取 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <!-- Spring Cloud & Alibaba -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- 注册中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- 配置中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--common基础依赖-->
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-common-redis</artifactId>
            <version>${jinyi-up.version}</version>
        </dependency>
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-common-mybatis-plus</artifactId>
            <version>${jinyi-up.version}</version>
        </dependency>
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-common-web</artifactId>
            <version>${jinyi-up.version}</version>
        </dependency>

        <!-- sa-token -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        <!-- 提供Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-user-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.jinyi.up</groupId>
            <artifactId>jinyi-user-api</artifactId>
            <version>1.0.0</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

启动类

import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

import java.net.InetAddress;
import java.net.UnknownHostException;

@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(value = "com.jinyi.up.user.mapper")
public class JinyiUserProviderApplication {

    public static void main(String[] args) throws UnknownHostException {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(JinyiUserProviderApplication.class, args);

        Environment env = applicationContext.getEnvironment();
        String ip = InetAddress.getLocalHost().getHostAddress();
        String port = env.getProperty("server.port");
//        String activePrn ofile = SpringContext.getActiveProfile();
        log.info("\n----------------------------------------------------------\n\t" +
                "Application is running! Access URLs:\n\t" +
                "Doc: \t\thttp://" + ip + ":" + port + "/doc.html\n\t" +
                "Swagger: \thttp://" + ip + ":" + port + "/swagger-ui/\n\t" +
                "----------------------------------------------------------");
    }
}

yml配置文件bootstrap.yml 和 application-dev.yml
bootstrap.yml 如下

server:
  port: 8060
  
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  application:
    name: jinyi-user-provider
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: dev
  cloud:
    nacos:
      # 注册中心
      discovery:
        server-addr: 1127.0.0.1:8848
      # 配置中心
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml  
        group: USER_GROUP 

#        shared-configs[0]:
#          data-id: jinyi-common.yaml
#          group: COMMON_GROUP
#          refresh: true

logging:
  level:
    spring.: DEBUG


# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: token
  # token有效期,单位秒,默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期),单位秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
  is-share: false
  # token风格
  token-style: uuid
  # 是否输出操作日志
  is-log: false
  # 是否从cookie中读取token
  is-read-cookie: false
  # 是否从head中读取token
  is-read-header: true
  # token前缀
#  token-prefix: Bearer

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:
  datasource:
    url: "jdbc:mysql://127.0.0.1:3306/jinyi_user_db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true"
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
#    driver-class-name: com.mysql.jdbc.Driver
    hikari:
      minimum-idle: 16
      maximum-pool-size: 40
      connection-timeout: 3000
      idle-timeout: 50000
      validation-timeout: 60000
      max-lifetime: 86370
  redis:
    # Redis数据库索引(默认为0)
    database: 1
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password: 
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 3000ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0

核心登录方法:

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.jinyi.up.common.base.result.R;
import com.jinyi.up.user.dto.UserAccountLoginDTO;
import com.jinyi.up.user.entity.JinyiRole;
import com.jinyi.up.user.entity.JinyiUser;
import com.jinyi.up.user.entity.JinyiUserRole;
import com.jinyi.up.user.service.IJinyiRoleService;
import com.jinyi.up.user.service.IJinyiUserRoleService;
import com.jinyi.up.user.service.IJinyiUserService;
import com.jinyi.up.user.vo.JinyiRoleVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Api(tags = "用户相关接口")
@RestController
@RequestMapping("/jinyiUser")
public class JinyiUserController {

    @Resource //用户服务
    private IJinyiUserService userService;
    @Resource //用户角色关联表服务
    private IJinyiUserRoleService userRoleService;
    @Resource //用户角色服务
    private IJinyiRoleService roleService;

    @ApiOperation(value = "用户账号登录", httpMethod = "POST")
    @PostMapping("/accountLogin")
    public R accountLogin(@RequestBody UserAccountLoginDTO userPhoneLoginDTO) {
        String account = userPhoneLoginDTO.getAccount();
        String password = userPhoneLoginDTO.getPassword();
        JinyiUser jinyiUser = userService.accountLogin(account, password);
        if (null != jinyiUser) {
        //密码校验通过后,直接调用登录
            StpUtil.login(jinyiUser.getId(), "PC");
            //登录成功后,返回相关的token信息
            return R.ok(StpUtil.getTokenInfo());
        }
        return R.failed("账号密码错误");
    }

    @ApiOperation(value = "查询用户对应的角色", httpMethod = "GET")
    @GetMapping("/getUserRoleByUserId/{userId}")
    public R<List<JinyiRoleVO>> getUserRoleByUserId(@PathVariable("userId") Long userId) {
        List<JinyiUserRole> userRoles = userRoleService.getByUserId(userId);
        if (CollectionUtils.isEmpty(userRoles)) {
            return R.ok();
        }
        Set<Integer> roleIds = userRoles.stream().map(JinyiUserRole::getRoleId).collect(Collectors.toSet());

        List<JinyiRole> roles = roleService.getByRoleIds(roleIds);
        if (CollectionUtils.isEmpty(roles)) {
            return R.ok();
        }
        List<JinyiRoleVO> jinyiRoleVOS = BeanUtil.copyToList(roles, JinyiRoleVO.class);
        return R.ok(jinyiRoleVOS);
    }

    @ApiOperation(value = "退出登录", httpMethod = "POST")
    @PostMapping("/userLogout")
    public R userLogout() {
        // 当前会话注销登录
        StpUtil.logout();
        return R.ok();
    }
}

最后测试

1.通过网关转发的登录
http://localhost:8070/user-api/jinyiUser/accountLogin
在这里插入图片描述
2.网关转发的其他需要登录带token的访问
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker 快速上手

目录 一、初始Docker 二、Docker基本操作 1、镜像操作命令 2、容器相关命令 3、数据卷 三、Deckerfile自定义镜像 1、镜像结构 2、自定义镜像 四、DockerCompose 一、初始Docker 镜像(lmage):Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起&am…

Docker中配置Mysql主从复制

新建主服务器容器实例3307 进入/mydata/mysql-master/conf目录下新建my.cnf vim my.cnf [mysqld] ## 设置server_id&#xff0c;同一局域网中需要唯一 server_id101 ## 指定不需要同步的数据库名称 binlog-ignore-dbmysql ## 开启二进制日志功能 log-binmall-mysql-bin …

Spring中读取和存储Bean对象(5000字详解)

在Spring项目的创建和使用(Spring项目的创建和使用_蜡笔小心眼子&#xff01;的博客-CSDN博客)中&#xff0c;我们详细讲解了如何通过配置XML文件存取Bean对象&#xff0c;但是没新增一个Bean对象时就需要添加一个Bean标签&#xff0c;而且如果同一个Bean对象被多次注册到Sprin…

轻松掌握K8S命名空间、Pod、Deployment部署工具

1、NameSpace命名空间 在官方可视化工具界面为如下 也可以使用kubectl命令行具体看使用命令行操作 2、Pod应用组 k8s所说的应用通常就是指pod&#xff0c;一个pod可以部署多个容器。容器间共享网络空间&#xff0c;共享存储 3、根据应用类型选择部署Pod的工具 k8s官方也有对…

运行Omniverse Replicator Headlessly

运行Replicator Headlessly 学习目标 本教程的目的是向您展示如何不费吹灰之力地使用 Replicator。 Headless 在这种情况下意味着计算设备没有显示器或外围设备&#xff0c;例如键盘和鼠标。 为了做到这一点&#xff0c;我们将对 Replicator 的核心功能 - “Hello World”中解…

MyBatis-Plus Generator v2.0.8 ~ v3.1.1 最新代码自动生成器

一、概述 官网&#xff1a;https://baomidou.com/ 官方文档 &#xff1a;https://baomidou.com/pages/56bac0/ 官方源码地址&#xff1a; https://gitee.com/baomidou/mybatis-plus 官方原话&#xff1a; AutoGenerator 是 MyBatis-Plus 的代码生成器&#xff0c;通过 Auto…

高级篇七、InnoDB数据存储结构

1、数据库的存储结构&#xff1a; 页 1.1 磁盘与内存交互的基本单位&#xff1a;页 1.2 页结构概述 页a&#xff0c;页b&#xff0c;页c … 页n 这些页可以不在物理结构上相连&#xff0c;只要通过双向链表相关联即可每个数据页中的记录会按照主键值从小到大的顺序组成一个单项…

Qt5.12实战之图形编程初识

演示效果: 1.绘制条件: 1. 绘图设备-> QPainter 2.画笔->QPen --->字体 (QFont) 3.画刷->QBrush-->自己定义画刷(QPixmap) 4.绘制事件->QPaintEvent 绘图步骤: 1.重写基类的虚函数 void paintEvent(QPaintEvent *event); 2.在虚函数 void paintEvent…

C++---状态压缩dp---愤怒的小鸟(每日一道算法2023.4.19)

注意事项&#xff1a; 难度警告&#xff01;这题在NOIP中也算偏难的题&#xff0c;量力而行。 本题为"状态压缩dp—最短Hamilton路径"的扩展题&#xff0c;建议先阅读这篇文章并理解。 本题是"重复覆盖问题"可以使用"Dancing Links"做&#xff0…

MySql-高级( 面试问题简析) 学习笔记

文章目录 1. MySql 中 MyISAM 和 InnoDB 存储引擎区别1.1. MyISAM1.2. InnoDB 2. 索引的数据结构2.1. B Tree索引2.2. BTree索引2.3. MySql 做的优化 3. 为什么使用BTree索引而不使用Hash索引&#xff1f;4. 为什么使用BTree索引而不使用B-Tree索引&#xff1f;5. MyISAM 存储引…

检测并打印C++编译器支持的feature(附Visual Studio 2022测试结果)

C标准快速迭代&#xff0c;不同的系统平台和编译器对C各种新功能的支持不同&#xff0c;通过这个程序可以测试所用编译器对各个版本C的支持情况。另一方面&#xff0c;可以在代码中通过这些宏针对不同版本编写不同的代码分支。 源码下面附上Visual Studio 2022的测试结果&#…

【字符串处理】

目录 总结&#xff1a; 只要一做字符串的题目必出bug&#xff0c; 本蒟蒻还是要开个专题写一下……懒狗直接引用chatgpt 在C中&#xff0c;我们可以使用以下几种方式进行字符串的输入&#xff1a; 1.使用输入运算符(>>): 可以按照空格分隔符把一个标准字符串(即不包含…

引用的底层原理(汇编指令),引用与指针的联系与区别

TIPS 2. 3. 4. 引用的底层本质 在语法层面上的话&#xff0c;这个引用是不开空间的&#xff0c;相当于是对一个变量进行一个取别名的这么一个操作。在底层实现上实际是有空间的&#xff0c;因为引用是按照指针方式来实现的。然而如果你从底层的角度去看的话&#xff0c;因…

两小时让你全方位的认识文件(完结)

上期阿博给友友们讲了一些关于文件的一些读写操作&#xff0c;这期给友友们分享一下二进制的方式和文件操作的一些误区&#xff0c;下面来跟着阿博走进文件吧&#x1f917;&#x1f917;&#x1f917; 文章目录 一.fread和fwrite功能介绍二.文件的随机读写三.文本文件和二进制文…

17.网络爬虫—Scrapy入门与实战

这里写目录标题 Scrapy基础Scrapy运行流程原理Scrapy的工作流程Scrapy的优点 Scrapy基本使用(豆瓣网为例)创建项目创建爬虫配置爬虫运行爬虫如何用python执行cmd命令数据解析打包数据打开管道pipeline使用注意点 后记 前言&#xff1a; &#x1f3d8;️&#x1f3d8;️个人简介…

第一章Git学习(尚硅谷新版Git快速入门)

文章目录 为什么要学习Git为什么要学习Git软件为什么要学习Git软件Git基础概念版本控制集中式、分布式版本控制的区别Git工作区域Git分支 版本号什么是版本号文件操作对应的版本号分支操作对应的原理 命令行操作Git相关配置的指令获取当前Git的配置信息名称和邮箱 Git文件操作相…

随笔-你买罐头干什么

生产环境不太稳定&#xff0c;正在挠头&#xff0c;想着怎么能解决这个问题。 聊天工具上突然弹出一张图片&#xff0c;是个不认识的人&#xff08;暂且称为Z&#xff09;发的。点进去一看&#xff0c;是从一个表格截取的一条数据&#xff0c;内容是我某次加班餐的订单。 Z&a…

带头单向链表源码及相关练习

目录 移除链表元素 链表的中间节点 链表倒数第k个节点 合并两个有序链表 相交链表 环形链表 环形链表2 分割链表 回文链表 public class MySingleList {//内部类的使用class ListNode {public int val;public ListNode next;public ListNode(int val) {this.val val;}…

Java基础:容器知识点

目录 1、Java容器都有哪些&#xff1f; 2、Collection 和 Collections 区别&#xff1f; 3、List、Set、Map 间的区别&#xff1f; 4、HashMap 和 Hashtable 区别&#xff1f; 5、如何决定用 HashMap 还是 TreeMap&#xff1f; 6、HashMap 的实现原理&#xff1f; 7、说…

浮点型在内存中的存储

常见的浮点数&#xff1a; 3.14159 1E10&#xff08;科学计数法&#xff1a;1.0*10^10&#xff09; 浮点数家族包括&#xff1a; float、double、long double 类型 浮点数表示的范围&#xff1a;float.h中定义 下面举一个例子&#xff1a; int main() {int n 9;float *pFloat…