业务参数与公共参数分离
我们目前的规范的公共参数和业务参数不放在一起。因为公共参数和业务参数一般不会在一起使用,如果只是为了获取公共参数就要将业务参数也一起反序列化有点浪费,而且涉及加签,如果将签名放在报文里势必要规定加密是的拼接规则等,而单独出来的公共参数可以直接使用。加签时直接将业务参数的字符串做签名即可。
JWT令牌处理
而由于我们与前端交互时使用的是JWT,JWT虽然在扩展性上有优势可以让我们方便的对接其他系统,但是在同一个业务集群内部如果每次服务间的调用都传递JWT会带来一些不必要的损耗,例如:JWT往往很长,而且每个服务都需要对JWT进行验签和解析。所以我们在网关上对JWT做验证和解析后将信息保存到Redis中,在后续的服务调用中仅传递JWT的JIT,这样其他服务需要JWT参数时可以直接通过JIT从Redis中获取对应的参数。
Feign 服务间调用传递通用参数
- 先通过过滤器将本服务的Request存储到ThreadLocal
# platform-module/platform-module-starter/platform-module-starter-web/src/main/java/com/tinem/platform/module/starter/web/filter/GatewayFilter.java
package com.tinem.platform.module.starter.web.filter;
import com.tinem.platform.module.starter.web.context.GatewayContext;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author fengzhihao
* @version v1
* @program: platform
* @className GatewayFilter
* @description TODO
* @site
* @company
* @create 2020-07-04 10:09
*/
public class GatewayFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
GatewayContext.REQUEST_LOCAL.set(request);
GatewayContext.RESPONSE_LOCAL.set(response);
try {
filterChain.doFilter(request,response);
}finally {
GatewayContext.REQUEST_LOCAL.remove();
GatewayContext.RESPONSE_LOCAL.remove();
}
}
}
- 使用Feign 的 RequestInterceptor构建调用其他服务的Request
# platform-module/platform-module-starter/platform-module-starter-web/src/main/java/com/tinem/platform/module/starter/web/config/FeignConfiguration.java
package com.tinem.platform.module.starter.web.config;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSON;
import com.tinem.platform.module.pojo.co.GatewayHeadName;
import com.tinem.platform.module.pojo.co.RedisKeyEnum;
import com.tinem.platform.module.pojo.vo.error.SystemException;
import com.tinem.platform.module.starter.sdk.MessageCode;
import com.tinem.platform.module.starter.web.context.GatewayContext;
import feign.FeignException;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author fengzhihao
* @version v1
* @program: platform
* @className FeignConfiguration
* @description TODO
* @site
* @company
* @create 2020-07-12 18:33
*/
@Slf4j
@Configuration
public class FeignConfiguration implements RequestInterceptor, Decoder {
public static final ThreadLocal<Map<String,String>> REQUEST_HEAD_LOCAL = new ThreadLocal<>();
public static void setClientId(String clientId){
String gatewayRequestId = UUID.randomUUID().toString();
Map<String,String> requestHead = new HashMap<>();
requestHead.put(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID,gatewayRequestId);
requestHead.put("clientId",clientId);
REQUEST_HEAD_LOCAL.set(requestHead);
}
@Resource
public StringRedisTemplate stringRedisTemplate;
@Override
public void apply(RequestTemplate template) {
// 如果有特定参数需要传递
Map<String,String> requestHead = REQUEST_HEAD_LOCAL.get();
if(requestHead != null){
String key = RedisKeyEnum.gateway_req_info.getKey(requestHead.get(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID));
stringRedisTemplate.opsForHash().put(key, GatewayHeadName.X_PLATFORM_GATEWAY_CONTEXT_CLIENT_ID,requestHead.get("clientId"));
stringRedisTemplate.expire(key,10, TimeUnit.MINUTES);
REQUEST_HEAD_LOCAL.remove();
}else {
// 获取本服务Request
HttpServletRequest request = GatewayContext.getRequest();
if(request == null){
// 如果无法获取到本服务Request,则生成新的请求
requestHead = new HashMap(){{
put(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID, UUID.randomUUID().toString());
put(GatewayHeadName.X_PLATFORM_GATEWAY_REQ_LANG, Locale.CHINESE.getLanguage());
}};;
}else {
// 将本服务所有以[x-platform-]开头的请求头提取出来
requestHead = new HashMap<>();
Enumeration enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()){
String name =(String) enumeration.nextElement();
if(!name.startsWith("x-platform-")){
continue;
}
String value = request.getHeader(name);
requestHead.put(name,value);
}
}
}
if(requestHead.get(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID) == null){
requestHead.put(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID,UUID.randomUUID().toString());
}
//设置请求头到构建的request
requestHead.entrySet().forEach(e->{
template.header(e.getKey(),e.getValue());
});
log.debug("feign client req url:{},gateway request id:{}",template.url(),template.headers().get(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID).stream().findFirst().get());
}
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
String requestId = response.request().headers().get(GatewayHeadName.X_PLATFORM_GATEWAY_REQUEST_ID).stream().findFirst().get();
log.debug("feign client req url:{},gateway request id:{}",response.request().url(),requestId);
String body = IoUtil.read(response.body().asInputStream(),"UTF-8");
if(response.status() != HttpStatus.OK.value()){
log.error("feign client http status:{}, error:{} ",response.status(),body);
throw new SystemException(MessageCode.ERROR_COMMONS_UNKNOWN);
}
if(response.headers().get(GatewayHeadName.X_PLATFORM_RES_CODE) == null){
throw new SystemException(MessageCode.ERROR_COMMONS_UNKNOWN);
}
String code = response.headers().get(GatewayHeadName.X_PLATFORM_RES_CODE).stream().findFirst().get();
String message = response.headers().get(GatewayHeadName.X_PLATFORM_RES_MESSAGE) == null?"":response.headers().get(GatewayHeadName.X_PLATFORM_RES_MESSAGE).stream().findFirst().get();
if(!MessageCode.SUCCESS.name().equals(code)){
message = Base64.decodeStr(message);
log.error("feign client code:{}, message:{} error:{}",code,message,body);
throw new SystemException(MessageCode.valueOf(code));
}
return JSON.parseObject(body,type);
}
}
Postman测试
检查测试日志
- 网关收到的的JWT
- Link服务获取到的jit
- Network服务收到Link服务传递的Jit