微服务-网关进阶-网关参数设计与封装

363 阅读3分钟

一、概述

在上一篇文章中(微服务-网关Spring Gateway进阶-日志跟踪唯一ID)JY @宋志宗 的指导下,发现了 SpringGateway实现日志跟踪唯一ID实现的问题,查找了一些资料,没有找到比较好的解决方案。最终我决定自己基于SpringBoot实现网关的功能。本文章还讲述了请求达到网关之后,对参数进行自定义封装,再传递给微服务,重点在于如何对请求参数进行设计与封装。

二、基于SpringBoot实现的网关

其工程结果如下图:

image.png

与SpringGateway实现的方式相比,区别主要有两点:

  • Filter改为Interceptor实现,
  • 增加了GatewayController作为网关的入口,网关入口的代码片段如下:
@RestController
public class GatewayController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private IApiCodeService apiCodeService;

    @RequestMapping("/api/{apiCode}")
    public String entry(@PathVariable("apiCode") String apiCode) {
        ApiCodeDVO apiCodeDVO = apiCodeService.getByApiCode(apiCode);
        if (apiCodeDVO == null) {
            throw new RuntimeException("unknow api");
        }
        String url = "http://" + apiCodeDVO.getSystemCode() + apiCodeDVO.getUrl();
        logger.info("gateway request url: {}", url);
        return restTemplate.getForObject(url, String.class);
    }

}

其逻辑主要如下:

  • 根据apiCode获取对应的服务信息,包含微服务code以及微服务的url;
  • 通过restTemplate调用对应的微服务,实现路由功能。

目前这个例子比较简单,只介绍了getForObject方式的调用,正常我们的请求都有参数,如何实现呢,请看网关参数的设计与封装。

三、网关参数的设计与封装

其主要设计思路如下:

image.png

其代码实现片段如下,注意调用微服务返回的数据格式是Map类型,这里我没想到理想的数据格式,用比较通用的Map类型来作为网关返回的数据类型:

@PostMapping("/api/{apiCode}")
public Map entry(@PathVariable("apiCode") String apiCode, HttpServletRequest request, @RequestBody Map body) {
    ApiCodeDVO apiCodeDVO = apiCodeService.getByApiCode(apiCode);
    if (apiCodeDVO == null) {
        throw new RuntimeException("unknow api");
    }
    String url = "http://" + apiCodeDVO.getSystemCode() + apiCodeDVO.getUrl();
    logger.info("gateway request url: {}", url);

    // 1. 封装Header
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.set("traceId", GatewayContextHolder.getContext().getTraceId());
    headers.set("startTime", GatewayContextHolder.getContext().getStartTime().toString());
    headers.set("userAgent", request.getHeader("user-agent"));
    headers.set("refer", request.getHeader("refer"));
    // 2.封装body参数,转换为json字符串
    String httpEntityBody = null;
    try {
        ObjectMapper objectMapper = new ObjectMapper();
        httpEntityBody = objectMapper.writeValueAsString(body);
    } catch (JsonProcessingException e) {
        logger.error(e.getMessage(), e);
        throw new RuntimeException("object write error");
    }
    HttpEntity<String> httpEntity = new HttpEntity<>(httpEntityBody, headers);
    // 3.调用微服务
    ResponseEntity<HashMap> responseEntity = restTemplate.postForEntity(url, httpEntity, HashMap.class);
    // 4.获取返回的消息
    return responseEntity.getBody();
}

这里微服务的实现如下,主要看返回对象BaseResponse的实现,返回给网关的时候,会转换为Map类型。

@RestController
public class NacosHelloController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping("/hello")
    public String hello() {
        logger.info("nacos hello");
        return "nacos hello";
    }

    @PostMapping("/hello")
    public BaseResponse<String> hello(@RequestBody HelloDTO request) {
        logger.info("nacos hello post");
        BaseResponse<String> baseResponse = new BaseResponse<>();
        baseResponse.setData(request.toString());
        return baseResponse;
    }
}

而BaseResponse的实现就比较简单,如下:

public class BaseResponse<T> {

    private T data;

    public T getData() {
        return data;
    }

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

    @Override
    public String toString() {
        return "BaseResponse{" +
                "data=" + data +
                '}';
    }
}

四、总结

在本例的源代码中,还实现了traceId的传递,其原理是通过ThreadLocal沟通了ContextHolder,在同一个线程中进行隐式传递,采用Inteceptor的方式进行实现。

4.1 网关中的实现

对每一个请求设置一个traceId,放在GatewayContext中。

public class TraceInfoInterceptor extends HandlerInterceptorAdapter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("====== TraceInfoFilter start ======");
        String traceId = TraceIdUtil.shortUUID();
        // 1.将traceId放到上下文中,后续传递给微服务
        GatewayContext context = GatewayContextHolder.getContext();
        context.setTraceId(traceId);
        GatewayContextHolder.setContext(context);
        // 2.将traceId设置到slf4j中,日志打印模板配置打印traceId
        MDC.put("traceId", traceId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }
}

4.2 微服务中的实现

通过网关封装request的对象中获取traceId。

public class RequestInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String traceId = request.getHeader("traceId");
        MDC.put("traceId", traceId);
        return super.preHandle(request, response, handler);
    }

}

五、源码

gitee.com/animal-fox_…