第二章 从spring-boot和spring-cloud开始

前言:

上一章已经把整个框架做了介绍,同时也略带了后续的微服务生态图。这章将从框架的搭建开始说起,spring-boot和spring-cloud对于看这篇文章的jr吗都不陌生,我们就从它们开始。

还是先附上项目结构图:
在这里插入图片描述

1.父层目录

是一个空的maven项目,在pom文件中定义spring-boot/clould和其他依赖的版本:spring-boot版本是:2.1.3.RELEASE
spring-cloud:版本是Greenwich.SR2;在父pom中以import的方式申明。
在这里插入图片描述

2.FW-BOOT

在这里插入图片描述
这个包相当简单,把Spring-boot的相关依赖统一放在这边管理,为什么这么做呢?之前在core包中集成大量的spring-boot包,导致pom文件相当臃肿不好管理;单独拎出来,会让整个pom变得很清晰,后续所有spring-boot的升级,新增依赖都只要在这个maven模块维护即可,很多人会有疑问这样做是不是有点多余,我的观点是,如果我们在这样的细节上做一些小的调整,能为整个框架维护和扩展带来一些好处的话是值得的,这种好处看似没啥,实际上渗透在每个框架开发人员中(自行体会吧)。这个模块不会对外暴露,只会在core中引用。ok,接下啦看下pom文件的引用:引入了spring-boot的相关依赖

<?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>fw-parent</artifactId>
        <groupId>com.mars.fw</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mars.fw</groupId>
    <artifactId>FW-BOOT</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--默认的tomcat插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>compile</scope>
        </dependency>

        <!--默认的tomcat插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--AOP-->
        <!--spring默认使用yml中的配置,但有时候要用传统的xml或properties配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--健康监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--健康监控-->
        <!--自动加载配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!--自动加载配置-->
    </dependencies>
</project>

3.FW-ClOUD

这个和FW-BOOT一样把spring-cloud的相关配置依赖抽取出来,在core中引用,目的是一样的;看下pom文件的引用:注意在这里面我引入了spring-cloud-starter-alibaba-nacos-config和spring-cloud-starter-alibaba-nacos-discovery,这是因为服务的注册中心我使用了nacos 而没有使用尤里卡;两种我都使用过,nacos在可视化界面和配置文件管理方面我认为不错,实践下来很刚。nacos的安装部署使用会写专门的章节来介绍使用。

<?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>fw-parent</artifactId>
        <groupId>com.mars.fw</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mars.fw</groupId>
    <artifactId>FW-CLOUD</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>MARS-FW-CLOUD-STARTER</name>
    <developers>
        <developer>
            <name>dengjinde</name>
            <email>[email protected]</email>
        </developer>
    </developers>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
    </dependencies>
</project>

4.FW-CORE

这个是核心,接下来围绕这个详细来讲解实践过程,没有很复杂难懂的东西。
在这里插入图片描述
从上面的目录结构来看:我把core分成了web模块和其他模块。先来讲下web模块:我的定义是所有有关提供API和WEB应用相关的操作都放在这个包中;我们来思考下对于一个web的应用需要具备哪些基础功能(实际上是对springMVC的封装和增强)。

4.1.core-web

1.WebMvcConfigurer的配置

要支持spring-web相关的功能首先要进行web相关的配置,这个大家都清楚了我们新建一个类来实现这个接口进行配置。
这里面主要处理了这么几个问题:

1.前端浏览器ajax的跨域问题
2.静态资源路径设置问题
3.接下来会讲到的统一参数返回的处理就是这个方法:
在这里插入图片描述

package com.mars.fw;

import com.mars.fw.web.reponse.handler.MarsRespValueHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author King
 * @Date 2020-04-20
 */
@Configuration
@EnableAspectJAutoProxy
@EnableAutoConfiguration
public class MarsWebMvcConfigure implements WebMvcConfigurer {

    /**
     * 实例请求映射处理适配器
     */
    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    /**
     * 解决跨域问题
     * <p>
     *
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }

    private CorsConfiguration addcorsConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        List<String> list = new ArrayList<>();
        list.add("*");
        corsConfiguration.setAllowedOrigins(list);

        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", addcorsConfig());
        return new CorsFilter(source);
    }

    /**
     * mvc 静态资源路径访问权限配置
     * 例如:registry.addResourceHandler("/upload/**").addResourceLocations("classpath:/upload/");
     * /upload/ 路径下的静态资源可以访问
     * <p>
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //所有目录都可以访问
        registry.addResourceHandler("/static/**").
                addResourceLocations("file:/usr/share/nginx/images/");

        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }


    /**
     * @PostConstruct 非静态的void()方法  尽量少在复杂的逻辑方法上注解这个 影响启动速度
     * <p>Tomcat6.x 以上
     * 服务器加载Servlet的时候运行,并且只会被服务器执行一次
     * <p>
     * 服务器加载Servlet的时候运行 执行实例请求映射处理适配器下自定义的参数拦截统一处理 返回标准数据结构
     */
    @PostConstruct
    public void initResponseValue() {
        final List<HandlerMethodReturnValueHandler> originalHandlers = new ArrayList<>(requestMappingHandlerAdapter.getReturnValueHandlers());

        RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
        for (int i = 0; i < originalHandlers.size(); i++) {
            final HandlerMethodReturnValueHandler valueHandler = originalHandlers.get(i);
            if (RequestResponseBodyMethodProcessor.class.isAssignableFrom(valueHandler.getClass())) {
                requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor) valueHandler;
                break;
            }
        }
        MarsRespValueHandler marsRespValueHandler = new MarsRespValueHandler(requestResponseBodyMethodProcessor);

        final int deferredPos = obtainValueHandlerPosition(originalHandlers, DeferredResultMethodReturnValueHandler.class);
        originalHandlers.add(deferredPos + 1, marsRespValueHandler);
        requestMappingHandlerAdapter.setReturnValueHandlers(originalHandlers);
    }

    private int obtainValueHandlerPosition(final List<HandlerMethodReturnValueHandler> originalHandlers, Class<?> handlerClass) {
        for (int i = 0; i < originalHandlers.size(); i++) {
            final HandlerMethodReturnValueHandler valueHandler = originalHandlers.get(i);
            if (handlerClass.isAssignableFrom(valueHandler.getClass())) {
                return i;
            }
        }
        return -1;
    }


}

2.core-web实现
4.1.2.1.统一数据返回处理实现

首先我们希望我们的API接口返回的数据格式是统一的,这边就设计到SpringMVC里面怎么封装统一的数据格式,原理这边不深讲。首先我们先定义系统中的统一返回码和统一返回数据格式:
在这里插入图片描述
定义一个IRCode的接口用来给业务层做自定义实现,同时定义一个King类来实现框架中包含的code和message;

package com.mars.fw.web.reponse;

/**
 * @Author King
 * @create 2020/4/20 11:43
 */
public interface IRCode {

    String message();

    int code();
}

package com.mars.fw.web.reponse;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import java.io.Serializable;

/**
 * @Author King
 * @create 2020/4/20 11:24
 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class King<T> implements Serializable {

    private static final long serialVersionUID = -2466174531203439862L;

    public static final Integer SUCCESS_CODE = 1000;
    public static final String SUCCESS_MSG = "操作成功";

    private int code;
    private String msg;
    private T data;


    public King() {
        this(SUCCESS_CODE, SUCCESS_MSG, null);
    }

    public King(IRCode irCode) {
        this(irCode.code(), irCode.message(), null);
    }

    public King(IRCode irCode, T data) {
        this(irCode.code(), irCode.message(), data);
    }

    public King(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> King<T> success(IRCode irCode, T data) {
        return new King(KingCode.SUCCESS, data);
    }

    public static <T> King<T> success(String message) {
        return new King(KingCode.SUCCESS, message);
    }

    public static <T> King<T> fail(IRCode irCode, T data) {
        return new King(KingCode.FAULT, data);
    }

    public static <T> King<T> fail(String message) {
        return new King(KingCode.FAULT, message);
    }

    public int getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

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

除此之外我们还需定义一个系统枚举的code:KingCode

package com.mars.fw.web.reponse;

/**
 * @Author King
 * @create 2020/4/20 11:52
 */
public enum KingCode implements IRCode {


    /**
     * 状态码定义
     */
    FAULT(9999, "失败"),
    SUCCESS(10000, "成功"),
    LOGIN_SUCCESS(10001, "登录成功"),
    LOGOUT_SUCCESS(10002, "登录失败"),
    PASS_WRONG(10002, "密码错误"),
    USER_NOT_FIND(10003, "用户不存在"),
    CAPTCHA_TIMEOUT(10004, "验证码失效"),
    CAPTCHA_ERROR(10005, "验证码错误"),
    LOGIN_LOCK(10006, "该账号已被关闭"),
    SMS_EXCEPTION(10007, "短信登录异常"),
    TOKEN_NOT_EMPTY(90000, "token不能为空"),
    TOKEN_ERROR(90001, "token校验失败"),
    TOKEN_EXCEPTION(90002, "token校验异常"),
    URL_EXPIRED(90003, "访问的URL过期"),
    DEFAULT_EXCEPTION(99999, "系统异常");

    final int code;
    final String message;

    @Override
    public int code() {
        return this.code;
    }

    @Override
    public String message() {
        return this.message;
    }

    private KingCode(final int code, final String message) {
        this.code = code;
        this.message = message;
    }
}

写到这边我发现这些不用贴上代码只讲核心实现就好,这些直接到源码去看。
核心的实现在我们自定义一个MarsRespValueHandler,这个类是实现了spring-web包中的HandlerMethodReturnValueHandler。这个handler是spring提供给我们的,用来处理controller层方法返回的结果处理器。我们可以重写里面的方法来得到我们需要的数据格式。代码里面我有详细的注释,handleReturnValue 是我们处理的关键,我们把我们处理后的值重新放回spring-web response的链路中也就是this.requestResponseBodyMethodProcessor.handleReturnValue。

package com.mars.fw.web.reponse.handler;

import com.mars.fw.web.reponse.King;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

/**
 * @description: mvc 返回值统一处理 统一处理成Response实体结构 标准输出到前端
 * @author:dengjinde
 * @date:2020/4/20
 */
public class MarsRespValueHandler implements HandlerMethodReturnValueHandler {

    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;

    /**
     * 构造函数
     *
     * @param requestResponseBodyMethodProcessor
     */
    public MarsRespValueHandler(RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor) {
        this.requestResponseBodyMethodProcessor = requestResponseBodyMethodProcessor;
    }

    /**
     * 当返回值为 true的时候才会开启自定义的handler
     *
     * @param returnType
     * @return
     */
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
    }

    /**
     * 自定义handler逻辑 当返回值为空的时候自动返回标准格式
     * 这边的逻辑可以根据需要扩展
     *
     * @param returnValue
     * @param returnType
     * @param mavContainer
     * @param webRequest
     * @throws Exception
     */
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        King response = null;
        if (!King.class.isAssignableFrom(returnType.getParameterType())) {
            response = new King(King.SUCCESS_CODE, "success", returnValue);
        } else {
            response = (King) returnValue;
        }
        this.requestResponseBodyMethodProcessor.handleReturnValue(response, returnType, mavContainer, webRequest);
    }
}

定义好这个handler后在前面的MarsWebMvcConfigure中,在指定的位置织入我们定义好的handler这样就可以实现自定义的数据处理并返回了。我们可以很自由的在这里面定义我们想要的数据格式,做更多的事情。实现的细节可以看源码。

4.1.2.2 统一异常处理

有了统一的数据格式返回,我们常常会遇到一些异常,直接返回异常肯定是不可取的。所以我们需要对异常统一处理,这边只讲核心实现,细节看源码。实现的原理是利用spring-web提供的@ControllerAdvice controller增强器实现的。
在这里插入图片描述

package com.mars.fw.web.exception.advice;

import com.mars.fw.web.exception.KingException;
import com.mars.fw.web.reponse.ExceptionCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.regex.Pattern;

/**
 * 统一系统异常处理
 *
 * @Author King
 * @create 2020/4/20 15:25
 */
@Slf4j
@ControllerAdvice
public class KingExceptionHandlerAdvice {


    /**
     * 统一异常处理
     *
     * @param request
     * @param response
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object controllerExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {

        String message = ExceptionCode.DEFAULT_EXCEPTION.message();

        if (KingException.class.isAssignableFrom(ex.getClass())) {
            KingException kingException = (KingException) ex;
            printExceptionLog(kingException, ex);
            return kingException.transferResponse();
        }

        if (ex instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException exception = (MissingServletRequestParameterException) ex;
            message = "必填 " + exception.getParameterType() + "类型参数 '" + exception.getParameterName() + "' 不存在";
        } else if (ex instanceof EmptyResultDataAccessException) {
            EmptyResultDataAccessException exception = (EmptyResultDataAccessException) ex;
            String pattern = "No class.*with.*exists!.*";
            boolean isMatch = Pattern.matches(pattern, ex.getMessage());
            if (isMatch) {
                message = "数据不存在,请确认";
            }
        } else {
            if (null != ex.getMessage()) {
                message = "系统异常,联系管理员";
            }
        }

        KingException kingException = new KingException(ExceptionCode.DEFAULT_EXCEPTION, message);
        printExceptionLog(kingException, ex);
        if (log.isDebugEnabled()) {
            log.debug(ex.getMessage(), ex.getStackTrace());
        }
        return kingException.transferResponse();

    }


    /**
     * 错误日志
     *
     * @param kingException
     */
    private void printExceptionLog(KingException kingException, Exception ex) {
        if (kingException.getCause() == null) {
            log.error("######ErrorCode: {},message: {}#######", kingException.getCode().code(), kingException.getMessage());
        } else {
            log.error("######ErrorCode: {},message: {}#######", kingException.getCode().code(), kingException.getMessage(), kingException);
        }
        log.error("######ErrorCode: {},message: {}#######", kingException.getCode().code(), kingException.getMessage(), ex);
    }

}

基于此我们根据团队自定义的异常,根据ExceptionHandler处理指定异常。

4.1.2.3 在线文档实现

API文档是很重要的,统一的API文档更为重要,不要研发们另外维护一套文档我觉得是更重要的。所以在线文档就显得很迫切,这边采用的是swagger2的升级版:knife4j-spring-ui
首先在pom中引入包:

  <!--框架其他核心功能依赖的第三方包-swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-ui</artifactId>
        </dependency>
  <!--框架其他核心功能依赖的第三方包-swagger -->
package com.mars.fw.web.doc;


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.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author King
 * @description swagger相关配置类
 * @date 2020/4/20
 */
@EnableSwagger2
@Configuration
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(true)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.mars"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {

        return new ApiInfoBuilder()
                .title("King 自定义 DOC 文档")
                .description("king-swagger-api")
                .termsOfServiceUrl("http://localhost:9098")
                .version("v0.1")
                .build();
    }
}

在这里插入图片描述

4.1.2.4 上下文会话

在微服务中现在几乎都是无状态的,与往常的session不同;更多的是使用token来做用户的信息认证,那么接口的用户信息的全局读取我们就要做一些处理。实现的方式也很多,我这边使用的是利用ThreadLocal,来做全局信息的保存。接口授权后,会将用户等全局信息保存到ThreadLocal中;ThreadLocal我们知道它是线程的副本,意味着每个线程都持有,是隔离的,在线程的生命周期中的任何地方都能取到。基于这些就能满足我们的需要,其实spring的事务,也是利用ThreaLocal的特性,当然有人可能会考虑到ThreadLocal的内存泄露问题,不过只要在实现的时候注意就可以避免这个问题;这边不做详细阐述,后续会在单独章节讲解这个。ok,我们来看下实现:
在这里插入图片描述
首先先定义一个全局的GlobalEntry 作为数据存储的载体,里面可以根据需要在自己的团队中定义;然后定义一个上下文KingContext,作为数据的管理类;紧接着在定义一个KingContextProvider接口,里面是可以定义Entry的获取方法,由业务侧根据需要做具体实现,这边主要是想约束GlobalEntry获取的实现 因为GlobalEntry是比较敏感或者说能影响全局的数据。

package com.mars.fw.web.context;

import com.mars.fw.web.exception.KingException;
import org.apache.commons.lang.StringUtils;

/**
 * 封装会话上下文 利用的是ThreadLocal的特性
 * 这个是使用来管理接口生命周期内的全局变量的 最多的应用场景是在 用户信息的存储
 * <p>
 * 这边的封装想尽量简化调用者的复杂度
 *
 * @Author King
 * @create 2020/4/21 17:27
 */
public class KingContext {

    private static ThreadLocal<GlobalEntry> CONTEXT = new ThreadLocal<GlobalEntry>();

    public static void setContext(GlobalEntry context) {
        CONTEXT.set(context);
    }

    public static void removeContext() {
        CONTEXT.remove();
    }

    /**
     * 获取用户ID
     *
     * @return
     */
    public static Long getUserId() {
        GlobalEntry entry = CONTEXT.get();
        Long userId = null == entry ? null : entry.getUserId();
        if (null == userId) {
            throw new KingException("请先登录");
        }
        return userId;
    }

    /**
     * 获取用户Code
     *
     * @return
     */
    public static String getUserCode() {
        GlobalEntry entry = CONTEXT.get();
        String userCode = null == entry ? null : entry.getUserCode();
        if (StringUtils.isBlank(userCode)) {
            throw new KingException("请先登录");
        }
        return userCode;
    }

    /**
     * 获取token
     *
     * @return
     */
    public static String getToken() {
        GlobalEntry entry = CONTEXT.get();
        String token = null == entry ? null : entry.getToken();
        if (org.springframework.util.StringUtils.isEmpty(token)) {
            throw new KingException("请先登录");
        }
        return token;
    }

    /**
     * 获取全局存储的信息
     *
     * @return
     */
    public static Object getObject() {
        GlobalEntry entry = CONTEXT.get();
        Object object = null == entry ? null : entry.getObject();
        if (org.springframework.util.StringUtils.isEmpty(object)) {
            throw new KingException("会话上下文没有设置");
        }
        return object;
    }
}

ok,这张的篇幅有点长,其他内容放到下章接着讲。
源码地址:码云源码地址

原创文章 10 获赞 11 访问量 2073

猜你喜欢

转载自blog.csdn.net/dengzisheng/article/details/106143678