全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发

1 异常处理

全局异常处理在common模块
为什么要使用全局异常处理:如果没有全局异常处理,比如不可预知的异常(空指针异常),返回浏览器的页面,人看上去就是一大堆乱码,非常的不好看。对人极其不友好,而且后端排查问题,看一大堆错误信息也不好排查,所以就用全局异常@GlobalExceptional进行封装。

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第1张图片
1.只要是异常都会被管理
2.不能直接返回用户404 500 400,要返回两种异常 可预知异常(自己抛的异常)、不可预知(系统抛出的系统)。
3.所有异常统一响应消息处理回用户:程序员手动抛出(参数有问题,校验逻辑);服务异常,网络异常,系统维护,策略包,系统系统等。(写在common模块,所有微服务都要用到异常)
@ControllerAdvice 控制器增强类
@ExceptionHandler(Exception.class)不可控异常
@ExceptionHandler(CustomException.class)可预知异常

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第2张图片

使得异常类生效的加个配置
在这里插入图片描述

把类在spring容器中进行加载。
一旦其他服务引用了common这个包,就能够用这个全局异常类。

2 登陆模块

2.1思路分析

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第3张图片

注册:知道怎么注册才知道怎么登录

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第4张图片
直接123456进行MD5加密,容易被破译。

登录

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第5张图片
登陆user微服务 需要依赖 model common feign-api(对外接口) springbootstarterweb springbootstartertest springcloudalibabanacosdiscovery springcloudalibabanacosconfig

路径 zjj-leadnews-service\zjj-leadnews-user\src\main\java\com.zjj.user(config|controller.v1|controller.v2|mapper|service|UserApplication(启动类)|
创建zjj-leadnews-user模块 直接在zjj-leadnews-service右键new model;Parent是zjj-leadnews-service,Name是zjj-leadnews-user。

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.zjj.user.mapper")
@EnableFeignClients(basePackages = "com.zjj.apis")
public class UserApplication {

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

日志输出

如果是linux的话,改一下路径
debug有更多的错误信息

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

>
    <!--定义日志文件的存储地址,使用绝对路径-->
    

    <!-- Console 输出设置 -->
    
        
            
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            utf8
        
    

    <!-- 按照每天生成日志文件 -->
    
        
            
            ${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
        
    

    <!-- 异步输出 -->
    
        
        0
        
        512
        
        
    


    >
        -ref ref="CONSOLE"/>
    >
    >
    >
        <!---ref ref="ASYNC"/>-->
        
        
    >
>

补充:
1.登陆的时候可以将用户信息 封装为UserInfo实体,然后ThreadLoacal.set(userInfo), 这样全局都可以使用。
2.游客登陆 没有输入用户名和密码,后端将用户名设置为0

2.2 user模块对应的引导类

package com.heima.user; 
import org.mybatis.spring.annotation.MapperScan; 
import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication 
@EnableDiscoveryClient@MapperScan("groupId(0).groupId(1).模块名.mapper") 
public class UserApplication { 
	public static void main(String[] args) {
		SpringApplication.run(UserApplication.class,args); 
	} 
}

2.3 bootstrap.yml

server:
  port: 51801
spring:
  application: #nacos 这个端口的名字
    name: leadnews-user # 该模块的名字 将该微服务给到注册中心
  cloud:
    nacos:
      discovery: #注册
        server-addr: 192.168.200.130:8848
      config: #配置中心
        server-addr: 192.168.200.130:8848
        file-extension: yml #剩下的配置在nacos中配置

2.4 nacos配置

1.在nacos中创建配置文件:leadnews-user.yml
2.

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    ### 注意
    url: jdbc:mysql://192.168.200.130:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC 
    username: root ###不确定
    password: root ###不确定
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml # :后不要空格
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名 mapper.xml中写的别名
  type-aliases-package: com.zjj.model.user.pojos

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第6张图片

全局的返回实体ResponseResult

responseResult.setData();
data 属性 封装了 一个Object对象和一个token字符串
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第7张图片

/**
 * 通用的结果返回类
 * @param 
 */
public class ResponseResult<T> implements Serializable {

    private String host;

    private Integer code;

    private String errorMessage;

    private T data;

    public ResponseResult() {
        this.code = 200;
    }

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.errorMessage = msg;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.errorMessage = msg;
    }

    public static ResponseResult errorResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.error(code, msg);
    }

    public static ResponseResult okResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.ok(code, null, msg);
    }

    public static ResponseResult okResult(Object data) {
        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
        if(data!=null) {
            result.setData(data);
        }
        return result;
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums){
        return setAppHttpCodeEnum(enums,enums.getErrorMessage());
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
        return setAppHttpCodeEnum(enums,errorMessage);
    }

    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
        return okResult(enums.getCode(),enums.getErrorMessage());
    }

    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
        return okResult(enums.getCode(),errorMessage);
    }

    public ResponseResult<?> error(Integer code, String msg) {
        this.code = code;
        this.errorMessage = msg;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data) {
        this.code = code;
        this.data = data;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.errorMessage = msg;
        return this;
    }

    public ResponseResult<?> ok(T data) {
        this.data = data;
        return this;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }


    public static void main(String[] args) {
        //前置
        /*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
        System.out.println(success.getCode());
        System.out.println(success.getErrorMessage());*/

        //查询一个对象
        /*Map map = new HashMap();
        map.put("name","zhangsan");
        map.put("age",18);
        ResponseResult result = ResponseResult.okResult(map);
        System.out.println(JSON.toJSONString(result));*/


        //新增,修改,删除  在项目中统一返回成功即可
      /* ResponseResult result = ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
        System.out.println(JSON.toJSONString(result));*/


        //根据不用的业务返回不同的提示信息  比如:当前操作需要登录、参数错误
        /*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        System.out.println(JSON.toJSONString(result));*/

        //查询分页信息
        /*PageResponseResult responseResult = new PageResponseResult(1,5,50);
        List list = new ArrayList();
        list.add("itcast");
        list.add("itheima");

        responseResult.setData(list);
        System.out.println(JSON.toJSONString(responseResult));*/

    }

}

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第8张图片

public static ResponseResult okResult(Object data) {
        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
        if(data!=null) {
            result.setData(data);
        }
        return result;
    }
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
        return okResult(enums.getCode(),errorMessage);
    }

枚举 AppHttpCodeEnum

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第9张图片

public enum AppHttpCodeEnum {

    // 成功段0
    SUCCESS(200,"操作成功"),
    // 登录段1~50
    NEED_LOGIN(1,"需要登录后操作"),
    LOGIN_PASSWORD_ERROR(2,"密码错误"),
    // TOKEN50~100
    TOKEN_INVALID(50,"无效的TOKEN"),
    TOKEN_EXPIRE(51,"TOKEN已过期"),
    TOKEN_REQUIRE(52,"TOKEN是必须的"),
    // SIGN验签 100~120
    SIGN_INVALID(100,"无效的SIGN"),
    SIG_TIMEOUT(101,"SIGN已过期"),
    // 参数错误 500~1000
    PARAM_REQUIRE(500,"缺少参数"),
    PARAM_INVALID(501,"无效参数"),
    PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
    SERVER_ERROR(503,"服务器内部错误"),
    // 数据错误 1000~2000
    DATA_EXIST(1000,"数据已经存在"),
    AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
    DATA_NOT_EXIST(1002,"数据不存在"),
    // 数据错误 3000~3500
    NO_OPERATOR_AUTH(3000,"无权限操作"),
    NEED_ADMIND(3001,"需要管理员权限"),

    // 自媒体文章 3501~3600
    MATERIAL_REFERENCE_FAIL(3501,"素材引用失效");

    int code;
    String errorMessage;

    AppHttpCodeEnum(int code, String errorMessage){
        this.code = code;
        this.errorMessage = errorMessage;
    }

    public int getCode() {
        return code;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
}

分页

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第10张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第11张图片

登陆的接口

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第12张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第13张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第14张图片
游客不生成token,全局过滤器无法解析会报错
http://localhost:xxxx/api/v1/login/lgoin_auth
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第15张图片
这里是user微服务返回的token

MybatisPlus的使用

ApUser dbUser = getOne(Wrappers.lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
mapper

@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}

service

public interface ApUserService extends IService<ApUser> {

    /**
     * app端登录
     * @param dto
     * @return
     */
    public ResponseResult login(LoginDto dto);
}

serviceImpl

@Service
@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
    /**
     * app端登录
     * @param dto
     * @return
     */
    @Override
    public ResponseResult login(LoginDto dto) {

        //1.正常登录
        if(StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())){
            //1.1 查询用户
            ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
            if(dbUser == null){
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
            }

            //1.2 比对密码
            String salt = dbUser.getSalt();
            String dbUserPassword = dbUser.getPassword();
            String pswd = DigestUtils.md5DigestAsHex((dto.getPassword() + salt).getBytes());
            if(!dbUserPassword.equals(pswd)){
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }

            //1.3 结果返回  user  token
            Map<String,Object> map = new HashMap<>();
            dbUser.setSalt("");
            dbUser.setPassword("");
            map.put("user",dbUser);

            map.put("token", AppJwtUtil.getToken(dbUser.getId().longValue())); //唯一主键来创建token
            return ResponseResult.okResult(map);
        }else {
            //2.游客登录
            Map<String,Object> map = new HashMap<>();
            map.put("token", AppJwtUtil.getToken(0l));
            return ResponseResult.okResult(map);
        }

    }
}

note:
两张表(just me)
ap_user App用户信息表
ap_user_realName App实名认证信息表
一般 记录 id自增主键、username、password(前端和后端各用一次盐)、salt、address、sex、nationality、phone、image、is_or_not_identity_certification是否实名认证、status正常为0异常为1、flag普通用户为0VIP用户为1、注册时间created_time注册后自动登陆等

swagger降低后端人员编写接口文档负担

访问地址 服务加端口

在这里插入图片描述
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第16张图片

1、model和common模块导入依赖
2、common导入swagger配置类

>
            >io.springfox>
            >springfox-swagger2>
        >
        >
            >io.springfox>
            >springfox-swagger-ui>
        >
package com.zjj.common.swagger;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

   @Bean
   public Docket buildDocket() {
      return new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(buildApiInfo())
              .select()
              // 要扫描的API(Controller)基础包
              .apis(RequestHandlerSelectors.basePackage("com.zjj"))
              .paths(PathSelectors.any())
              .build();
   }

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("程序员","","");
      return new ApiInfoBuilder()
              .title("头条-平台管理API文档")
              .description("头条后台api")
              .contact(contact)
              .version("1.0.0").build();
   }
}
common模块配置类的生效
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zjj.common.exception.ExceptionCatch,\
  com.zjj.common.swagger.SwaggerConfiguration,\
  com.zjj.common.swagger.Swagger2Configuration,\
  com.zjj.common.aliyun.GreenImageScan,\
  com.zjj.common.aliyun.GreenTextScan,\
  com.zjj.common.tess4j.Tess4jClient,\
  com.zjj.common.redis.CacheService

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第17张图片

@RestController
@RequestMapping("/api/v1/login")
@Api(value = "app端用户登录",tags = "ap_user")
public class ApUserLoginController {

    @Autowired
    private ApUserService apUserService;

    @ApiOperation("app端用户登录")
    @PostMapping("/login_auth")
    public ResponseResult login(@RequestBody LoginDto dto){
//        if(true){
//            throw new CustomException(AppHttpCodeEnum.NEED_ADMIND);
//        }
//        int a = 1/0;
        return apUserService.login(dto);
    }
}
@Data
public class LoginDto {

    /**
     * 手机号
     */
    @ApiModelProperty(value = "手机号",required = true)
    private String phone;

    /**
     * 密码
     */
    @ApiModelProperty(value = "密码",required = true)
    private String password;
}

knife4j

在这里插入图片描述
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第18张图片

集成了swagger 针对swagger进行封装
可以下载离线文档,可以让别人补全这个文档
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第19张图片
在common模块即可

		>
            >com.github.xiaoymin>
            >knife4j-spring-boot-starter>
        >
package com.zjj.common.swagger;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Configuration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //分组名称
                .groupName("1.0")
                .select()
                //这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.zjj"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("头条API文档")
                .description("头条API文档")
                .version("1.0")
                .build();
    }
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zjj.common.swagger.Swagger2Configuration

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第20张图片

3 gateway

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第21张图片

全局过滤器来做JWT校验

与service类似 多少了服务就应该有多少个网关
zjj-leadnews-service\zjj-leadnews-user
zjj-leadnews-gateway\zjj-leadnews-user-gateway
user写成了app 因为我们做的是app 登陆就是用这个app登陆的

>
        >8>
        >8>
    >

    >
        >
            >org.springframework.cloud>
            >spring-cloud-starter-gateway>
        >
        >
            >com.alibaba.cloud>
            >spring-cloud-starter-alibaba-nacos-discovery>
        >
        >
            >com.alibaba.cloud>
            >spring-cloud-starter-alibaba-nacos-config>
        >
        >
            >io.jsonwebtoken>
            >jjwt>
        >
    >
package com.zjj.app.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class AppGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppGatewayApplication.class,args);
    }
}
生成bootstrap.yml
server:
  port: 51601
spring:
  application:
    name: leadnews-app-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.130:8848
      config:
        server-addr: 192.168.200.130:8848
        file-extension: yml

nacos网关跨域设置

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true #跨域 后期用的
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*" ##允许所以域 都可以访问该问服务
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      routes:
        # 平台管理
        - id: user #随便写
          uri: lb://leadnews-user #均衡负载 给到某个微服务器去访问 可以创建多个登录的微服务
          predicates: #断言 请求该微服务必须要/user localhost:51601/user/api/v1/login/...
          			# 去掉断言前缀 实际访问的是 leadnews-user对应的ip和port/api/v1
            - Path=/user/**
          filters:
            - StripPrefix= 1 #伪装 访问的时候可以去掉user

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第22张图片

route路由如何实现

StripPrefix=1 去掉前缀一个 /user/** 去掉前缀user user的作用表示连接的是user微服务 实际地址会去掉user 可以认为user是伪装
访问ip
http://localhost:8092(网关ip)/user/api/v1/login/login_in => leadnews-user微服务的api/v1/login/login_in接口
predicates 谓词,判断

网关全局过滤器给用户授权

注册的代码在哪里?没讲,大概的思路就是输入账号密码,然后MD5(密码+salt(DDUtil.random())),把salt和MD5加密后的密码存到数据库。每个用户都有唯一的一个salt保存在数据库。
用户登陆的时候返回token 用户每次请求(除了登陆请求)会携带token,会被网关全局过滤器拦截

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第23张图片

401统一为认证失败的代码

404 找不到任何东西
传token的方式 因为是在headers拿 所有封装在headers给
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第24张图片

监听器 > 过滤器(AOP日志记录) > 拦截器(AOP日志记录) > servlet执行 > 拦截器 > 过滤器 > 监听器
可以说URL是URI(URL是URI的子集)
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第25张图片
过滤器

@Component //放入到spring容器才能使得该类生效
@Slf4j
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //判断是否为登录 可以说URL是URI(URL是URI的子集)URL和URI之间的区别是什么
        if(request.getURI().getPath().contains("login")){
            //放行
            return chain.filter(exchange);
        }

        //校验token是否存在 Request Headers 里面 包含 为key的token 还包含 Accept-Encoding、Cache-Control Cookie Host Origin User-Agent、Connection、Content-Type等
        String token = request.getHeaders().getFirst("token");
        if(StringUtils.isEmpty(token)){
            //返回401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //结束请求
            return response.setComplete();
        }

        //token是否有效 之前是用AppJwtUtil.getToken(主键id)得到的token
        try {
            Claims claims = AppJwtUtil.getClaimsBody(token);
            //解析完得到的claims然后判断是否过期 -1:有效,0:有效,1:过期,2:过期
            int result = AppJwtUtil.verifyToken(claims);
            if(result == 1 || result == 2){
                //返回401
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            
            //获取用户id claims里面包含 {"id","userId"} 放到headers中 这样拦截器或者其他就能拿到headers中的id
            Object userId = claims.get("id");
            ServerHttpRequest serverHttpRequest = request.mutate().headers(new Consumer<HttpHeaders>() {
                @Override
                public void accept(HttpHeaders httpHeaders) {
                    httpHeaders.add("userId", userId + "");
                }
            }).build();
            //重置请求
            exchange.mutate().request(serverHttpRequest);
            
        }catch (Exception e){
            //打印堆栈错误信息
            e.printStackTrace();
            //返回401 解析失败
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //放行
        return chain.filter(exchange);
    }

    /**
     * 优先级设置 值越小,优先级越高
     * 过滤器的数量  过滤器0->过滤器1—>...controller
     * 监听器 > 过滤器(AOP日志记录) > 拦截器(AOP日志记录) > servlet执行 > 拦截器 > 过滤器 > 监听器
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

AppJwtUtil

public class AppJwtUtil {

    // TOKEN的有效期一天(S) 1*24*60*60
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

    // 生产ID
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);  // claims.get("id") 拿到id 你输入的就是id claims实际上是个map
        // claimMaps.put("userName", name); map里面随便你存什么信息
        long currentTime = System.currentTimeMillis(); // 毫秒
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("zjj") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     * 该方法用于解析token
     * 根据token拿到Claims对象
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

    /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    public static void main(String[] args) {
       /* Map map = new HashMap(); 
        map.put("id","1102");*/
        System.out.println(AppJwtUtil.getToken(1102L)); 
        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
        Claims claims = jws.getBody();
        System.out.println(claims.get("id"));  // claims.get("id") 拿到id 你输入的就是id claims实际上是个map

    }

}

app前段项目集成

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第26张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第27张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第28张图片
index.html里面的地址有app就会访问下面的地址然后到upstream
反向代理找到网关 反向代理中间件帮后端找到网关服务器
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第29张图片
nginx -s reload
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第30张图片

自媒体前端搭建 nginx

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第31张图片
D:\file\001developer\nginx-1.18.0\conf\leadnews.conf
一个nginx可以访问多了conf文件 可以有多个静态页面
多了一个heima-leadnews-wemedia.conf
重启的话 nginx -s reload
在这里插入图片描述
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第32张图片

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第33张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第34张图片

jwt java web token 以及 拦截器和生效 token被全局过滤器拿到然后把id设置到headers请求 然后拦截器拦截

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第35张图片
对称加密 密钥都一样 加密解密 TOKEN_ENCRY_KEY都一样
非对称加密 KEY不一样
不可逆加密 token解析不出来token

A.B.C

文件参数 文件上传 heima-file-starter minio工具

全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第36张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第37张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第38张图片
heima创建的一个依赖
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第39张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第40张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第41张图片
全局异常处理-dao-service-controller-gateway网关工程-集中处理token-登陆模块-获取用户信息-ThreadLocal-组件-项目环境推荐-swagger-knif转发_第42张图片

package com.heima.utils.thread;

import com.heima.model.wemedia.pojos.WmUser;

public class WmThreadLocalUtil {

    private static final ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();

    //存入线程 静态方法方便调用
    public static void setUser(WmUser wmUser){
        WM_USER_THREAD_LOCAL.set(wmUser);
    }

    //从线程中获取
    public static WmUser getUser(){
        return WM_USER_THREAD_LOCAL.get();
    }

    //清理
    public static void clear(){
        WM_USER_THREAD_LOCAL.remove();
    }
}

package com.heima.wemedia.config;

import com.heima.wemedia.interceptor.WmTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//使得拦截器生效
@Configuration
public class WebMvcConfig  implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
    }
}

package com.heima.wemedia.interceptor;

import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// public class WebMvcConfig  implements WebMvcConfigurer 使得拦截器生效
public class WmTokenInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取用户数据
        String userId = request.getHeader("userId");
        if (StringUtils.isNotBlank(userId)) {
            WmUser wmUser = new WmUser();
            wmUser.setId(Integer.valueOf(userId));
            WmThreadLocalUtil.setUser(wmUser);
        }
        return true;
    }

    /*@Override //抛异常不会走这里 清理不了数据 所以不用这个方法
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }*/

    /**
     * 抛异常会走这 且用来清理数据 防止内存溢出
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        WmThreadLocalUtil.clear();
    }
}

项目环境 project Environment 自我感觉稳定版本

maven - 3.3.9 (现在学习用的)/ 3.6.1
jdk 1.8
Intellij Idea 2020.1.2
Git

你可能感兴趣的:(gateway,java,微服务)