satoken+ gateway网关统一鉴权 初版

一:感谢大佬

本博客内容 参考了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
parent标签里只是指明了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的访问
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值