编程思想:设计模式如何在项目中优雅落地

基于个人的经验,谈谈设计模式在项目开发中的应用。因为是经验之谈,没有绝对的对与错。

下面整理的是我最常使用的设计模式,我用设计模式的前提是

  • 让代码的可读性变强
  • 能支持日后功能扩展

单例

目的

保证全局只有一个实例,防止因为频繁的创建、销毁对象而造成不必要的性能开销。

在网关项目中,单例模式是出现频率最高的模式。同时,所有的单例对象被 IoC 框架 Guice 统一管理。

**场景 1 **

网关会处理各种逻辑。一般将业务逻辑从主流程中抽取出来,封装在一个独立对象中。可以使用单例模式来保证全局唯一,使用注解 Singleton 来表示这个一个单例:

java 复制代码
@Singleton
public class HttpMethodPipeline {
  private List<HttpMethodHandler> handlers = new ArrayList<>();
  ...
}

使用注解 Inject 来注入对象

java 复制代码
public class ApiRewriteFilter extends HttpInboundSyncFilter{

  @Inject
  private HttpMethodPipeline pipeline;

  @Override
  public HttpRequestMessage apply(HttpRequestMessage request) {
    ...  
    pipeline.process(request);
    ...
  }
}

减少 if-else

过多的 if-else 会导致

  • 可读性变差
  • 难以扩展维护
  • 质量不可控,健壮性差
  • 不利于单元测试

但是另一方面 if-else 是无法回避的代码。所以,为了让程序变得优雅,下面几种模式是我使用频次很高的模式,意在消除 if-else 代码段带来的负面影响。

1.表驱动法(策略)

目的

用表结构来驱动业务逻辑,减少 if-else 。这里的表结构可以参考 HashMap,通过对 Key 计算出 hash 从而快速获取数据

示例

以之前的游戏项目中一段代码举例,需要计算出当前的英雄的级别:

  • 小于 80:等级 G
  • 80 至140:等级 F
  • 140 至 200:等级 E
  • ...

使用表驱动法来计算等级的话,非常方便,只要预先定义好表即可,整体不会出现一行 if-else 代码,如下所示:

java 复制代码
 public static String GetRoleAttributeClass(int attributeLv99) {

        Map<Integer,String> attributes = new HashMap<Integer, String>()
        {
            { 080, "G" },//  <=80 -> G
            { 140, "F" },//  >80 && <=140 -> F
            { 200, "E" },
            { 260, "D" },
            { 320, "C" },
            { 380, "B" },
            { 440, "A" },
            { 500, "S" },
        };
        var attributeClass = "?";
        foreach (var key in attributes.Keys.OrderBy(o=>o))
        {
            if (attributeLv99 <= key)
            {
                attributeClass = attributes[key];
                break;
            }
        }

        return attributeClass;
    }

当表驱动法+策略模式组合在一起时,可以极大的扩展系统。

场景 1

开放网关最初只支持 AppId+Secret 形式校验,但随着业务发展,为了满足不同的场景,需支持

  • 简单认证,即 AppId+内网
    • 携带请求头:X-Tsign-Open-Auth-Model=simple 来告知网关走哪种模式鉴权
  • Token 认证
    • 携带请求头:X-Tsign-Open-Auth-Mode=token 来告知网关走哪种模式鉴权
  • 签名验签认证
    • 携带请求头:X-Tsign-Open-Auth-Mode=signature 来告知网关走哪种模式鉴权
  • 默认 AppId+Secret
    • 携带请求头:X-Tsign-Open-Auth-Mode=signature 来告知网关走哪种模式鉴权

很显然,这是一种典型的横向扩展需求,鉴权模式会随着业务的发展而扩展。如果通过 if-else 将处理逻辑杂糅在主流程中,势必会造成越来越臃肿。

使用策略模式+表驱动法,可以有效缓解这种处境。

a.) 定义鉴权策略

java 复制代码
public interface AuthStrategy {
    Observable<HttpRequestMessage> auth(HttpRequestMessage request) throws Exception;
}

b.) 定义不同的策略实现类

  • SimpleAuthStrategy
  • TokenAuthStrategy
  • SignatureAuthStrategy
  • SecretAuthStrategy

c.)通过 Guice 来定义表,即映射关系,映射的 Key= X-Tsign-Open-Auth-Model 传递过来的鉴权模式,Value=具体的实现类

java 复制代码
MapBinder<String, AbstractAuthStrategy> authStrategyMapBinder = MapBinder.newMapBinder(binder(), String.class, AbstractAuthStrategy.class);
        authStrategyMapBinder.addBinding(OpenProtocol.SIMPLE_AUTH_STRATEGY).to(SimpleAuthStrategy.class);
        authStrategyMapBinder.addBinding(OpenProtocol.TOKEN_AUTH_STRATEGY).to(TokenAuthStrategy.class);
        authStrategyMapBinder.addBinding(OpenProtocol.SIGNATURE_AUTH_STRATEGY).to(SignatureAuthStrategy.class);
        authStrategyMapBinder.addBinding(OpenProtocol.SECRET_AUTH_STRATEGY).to(SecretAuthStrategy.class);

d.) 在主流程中,根据鉴权模式,获取到对象的策略对象

java 复制代码
@Slf4j
@Singleton
public class OpenAuthFilter extends HttpInboundFilter implements OpenProtocol {

    @Inject
    private Map<String, AbstractAuthStrategy> strategies;

    @Configuration("${open.auth.default.mode}")
    private String AUTH_DEFAULT_MODE ="secret";

    @Override
    public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage request) {
        //获取身份校验模式,如果不指定则使用默认的
        String mode=StringUtils.defaultIfEmpty(request.getHeaders().getFirst(AUTH_MODE), AUTH_DEFAULT_MODE).toLowerCase();
        //根据模式选择对应的策略
        AbstractAuthStrategy authStrategy = strategies.get(mode);
        if (authStrategy == null) {
            route2Error(ctx, Problem.valueOf(Status.UNAUTHORIZED));
            return Observable.just(request);
        }
        try {
            return authStrategy.auth(request);
        } catch (Exception cause) {
            logger.error("authentication failed.{}", cause);
            route2Error(ctx, Problem.valueOf(Status.UNAUTHORIZED));
            return Observable.just(request);
        }
    }
}

2.职责链

目的

一个逻辑可能由多种处理模式,通过将这些处理模式连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递。

场景 1

网关需要对 HTTP Method 进行适配,比如小程序客户端 Http Method 不支持 Put/Delete ,只支持 Get/Post。所以一般情况下使用 Post 来代替 Put/Delete,同时可能通过以下几种方式:

  • 请求头

  • 请求参数

  • 请求 Body

来告诉网关真正的 Method,所以网关需要对 HTTP Method 支持适配。可以职责链来实现这个需求:

java 复制代码
 public class HttpMethodPipeline {

  private List<HttpMethodHandler> handlers = new ArrayList<>();

  @PostConstruct
  private void init() {
    //第一优先级
    handlers.add(new InHeaderHttpMethodHandler());
    //第二优先级
    handlers.add(new InParameterHttpMethodHandler());
    //默认优先级,兜底方案
    handlers.add(new DefaultHttpMethodHandler());
  }

  public String process(HttpRequestMessage request) {
    try {
      for (HttpMethodHandler handler : handlers) {
        if (handler.shouldFilter(request)) {
          return handler.apply(request);
        }
      }
    } catch (Exception cause) {
      logger.error("{}", cause);
    }
    //容错方案
    return request.getMethod();
  }
}

场景 2

网关对用户 Token 鉴权时,需要对比 Token 中授权人的 Id是否与接口入参 accountId 保持一致。同时,这个 accountId 有可能位于

  • Path
  • Header
  • Request Parameter
  • Request Body

只要满足一个条件即可,通过职责链模式,可以有效解决问题,避免 if-else 带来的扩展麻烦

java 复制代码
    private HttpRequestMessage postAuthentication(HttpRequestMessage request, MatchApi matchApi) {
       
        if (TokenMode.USER.toString().equals(tokenMode)) {
            //验证不过
            if (!validatorEngine.run(
                AuthContext
                    .builder()
                    .request(request)
                    .matchApi(matchApi)
                    .build())) {
                route2Error(ctx, Problem.valueOf(Status.UNAUTHORIZED));
            }
        }
        return request;
    }

定义验证引擎,本职上是一个处理链

java 复制代码
    class ValidatorEngine {
        private List<AuthValidator> validators=new ArrayList<>();
        ScopeValidator scopeValidator=new ScopeValidator();
        ValidatorEngine(){
            validators.add(new PathValidator());
            validators.add(new HeaderValidatorEngine());
            validators.add(new BodyValidator());
            validators.add(new ParameterValidator());
        }

        boolean run(AuthContext authContext){
            boolean pass=false;
            try {
                if (scopeValidator.validate(authContext)){
                    for (AuthValidator validator : validators) {
                        if (validator.validate(authContext)){
                            pass=true;
                            break;
                        }
                    }
                }
            }catch (Exception cause){
                pass=true;
                logger.error("",cause);
            }
            return pass;
        }
    }

简单工厂

目的

提供创建实例的功能,而无需关心具体实现,彼此之间互相解耦。往往和策略模式组合使用,即从工厂中获取一个策略。

场景 1

根据灰度配置获取灰度策略

java 复制代码
public interface RuleStrategyFactory {
    RuleStrategy getRuleStrategy(GrayRule grayRule);
}

场景 2

获取远程服务

java 复制代码
public interface NettyOriginFactory {
  NettyOrigin create(@Assisted("name") String name, @Assisted("vip") String vip, int defaultMaxRetry, Routing routing);
}

场景 3

根据 Uri 获取模板

java 复制代码
public interface UriTemplateFactory {
    UriTemplate create(String name);
}

场景 4

获取 WAF 拦截处理器

java 复制代码
@Singleton
public class InboundRuleMatcherFactory {
  public InboundRuleMatcher create(RuleDefinition definition) {
    InboundRuleMatcher inboundRuleMatcher = null;
    switch (definition.getRuleStage()) {
      case CLIENT_IP:
        inboundRuleMatcher = new ClientIPRuleMatcher(definition);
        break;
      case CONTENT_TYPE:
        inboundRuleMatcher = new ContentTypeRuleMatcher(definition);
        break;
      case CONTENT_LENGTH:
        inboundRuleMatcher = new ContentLengthRuleMatcher(definition);
        break;
      case USER_AGENT:
        inboundRuleMatcher = new UserAgentRuleMatcher(definition);
        break;
      case REQUEST_ARGS:
        inboundRuleMatcher = new RequestArgsRuleMatcher(definition);
        break;
      case COOKIES:
        inboundRuleMatcher = new CookieRuleMatcher(definition);
        break;
      default:
        break;
    }
    return inboundRuleMatcher;
  }
}

简单工厂可以和表驱动法组合使用,这样会非常清爽:

java 复制代码
@Singleton
@Slf4j
public class DefaultFlowStrategyFactory implements FlowStrategyFactory {

    @Inject
    private Injector injector;

    private static final ImmutableMap<LBAlgorithmType, Class<? extends AbstractFlowStrategy>> map = ImmutableMap.of(
            LBAlgorithmType.RANDOM, RandomFlowStrategy.class,
            LBAlgorithmType.ROUND_ROBIN, RoundRobinFlowStrategy.class,
            LBAlgorithmType.WEIGHTED, WeightedFlowStrategy.class);

    @Override
    public FlowStrategy getFlowStrategy(Flow flow) {
        AbstractFlowStrategy strategy = null;
        Class<? extends AbstractFlowStrategy> clazz = map.get(flow.getAlgorithm());
        if (clazz != null) {
            strategy = injector.getInstance(clazz);
        }
        if (strategy == null) {
            //容错机制,如果配置了非 RANDOM、ROUND_ROBIN、WEIGHTED 算法,或者忘记设置,默认返回 RANDOM
            strategy = new RandomFlowStrategy();
        }
        strategy.apply(flow.getValue());
        return strategy;
    }
}

模板方法

目的

定义处理逻辑的通用骨架,将差异化延迟到子类实现

场景 1

鉴权通过时,新老开放网关向下游传递的数据有差异,所有数据都会存储在 SessionContext中,通过模板方法定义通用骨架:

java 复制代码
public abstract class AbstractAppPropertyStash implements AppPropertyStash {
  @Override
  public void apply(AppEntity appEntity, HttpRequestMessage request){
    SessionContext context = request.getContext();
    //记得在使用方做容错处理:DefaultValue
    context.set(APP_IP_WHITE_LIST_CTX_KEY, appEntity.getIps());
    context.set(APP_THROTTLE_INTERVAL_CTX_KEY, appEntity.getInterval());
    context.set(APP_THROTTLE_THRESHOLD_CTX_KEY, appEntity.getThreshold());
    store(appEntity,request);
  }
  protected abstract void store(AppEntity appEntity, HttpRequestMessage request);
}

对于新开放网关,向下游传递USER_ID

java 复制代码
public class DefaultAppPropertyStash extends AbstractAppPropertyStash {
  @Override
  protected void store(AppEntity appEntity, HttpRequestMessage request) {
    SessionContext context = request.getContext();
    request.getHeaders().set(USER_ID, appEntity.getGId());
    context.set(USER_ID, appEntity.getGId());
  }
}

对于老开放网关,向下游传递LOGIN_ID

java 复制代码
public class DefaultAppPropertyStash extends AbstractAppPropertyStash {
  @Override
  protected void store(AppEntity appEntity, HttpRequestMessage request) {
    String gId = appEntity.getGId();
    String oId = appEntity.getOId();
    if (StringUtils.isNotEmpty(oId)) {
      request.getHeaders().add(X_TSIGN_LOGIN_ID, oId);
      request.getContext().set(X_TSIGN_LOGIN_ID, oId);
    } else {
      request.getHeaders().add(X_TSIGN_LOGIN_ID, "GID$$" + gId);
      request.getContext().set(X_TSIGN_LOGIN_ID, "GID$$" + gId);
    }
  }

所以 USER_IDLOGIN_ID就是差异化的表现,由各子类负责。

场景 2

网关支持灰度发布,即通过服务分组来将请求路由到指定分组的服务,通过定义模板,获取分组信息:group,然后差异化的路由由子类实现,比如:RandomFlowStrategyRoundRobinFlowStrategyWeightedFlowStrategy等。

java 复制代码
public abstract class AbstractFlowStrategy implements FlowStrategy {

    protected List<String> groups;

    public void apply(Map<String, String> value) {
        groups = Arrays.asList(value.get("group").split(";"));
        preHandle(value);
    }

    protected abstract void preHandle(Map<String, String> value);

}

观察者

目的

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,彼此之间解耦。

场景 1

为了提高网关的响应,一般会将常用数据 LRU 缓存到本地。比如 WAF 拦截规则会预先从数据库中读取出来,同时这部分数据存在变更的可能,虽然频次很低,但还是每隔 5min 从数据库读取到内存中。

对于构建 WAF 规则是耗时的事情,特别是它需要正则表达式编译。故通过观察者模式,当感知到数据发生变化时,才通知下游处理程序构建 WAF 规则,如下 WAF 拦截规则即主题:

java 复制代码
public class DynamicValue implements Value {

  private Set<Observer> observers = Collections.synchronizedSet(new HashSet<>());

  private Set<RuleDefinition> value = null;

  public DynamicValue() {
  }

  public DynamicValue(Set<RuleDefinition>  value) {
    super();
    this.value = value;
  }

  @Override
  public void addObserver(Observer observer) {
    observers.add(observer);
  }

  @Override
  public void removeObserver(Observer observer) {
    observers.remove(observer);
  }

  @Override
  public boolean updateValue(Set<RuleDefinition> newValue) {
    if (isEqual(value, newValue)) {
      return false;
    }
    value = newValue;
    //规则变化,更新
    for (Observer observer : observers) {
      observer.onChange(newValue);
    }
    return true;
  }

  @Override
  public void clear() {
    observers.clear();
  }

  private boolean isEqual(Set<RuleDefinition>  oldValue, Set<RuleDefinition> newValue) {
    if (oldValue == null && newValue == null) {
      return true;
    }
    if (oldValue == null) {
      return false;
    }
    return oldValue.equals(newValue);
  }
}

下游的处理程序作为观察者:

java 复制代码
public class RuleManager {
  private Value wafDynamicValue = new DynamicValue();
    
  public void register(Observer observer){
    wafDynamicValue.addObserver(observer);
  }
}

场景 2

网关会将所有的 HTTP 请求、响应发送到 Kafka 中。虽然本身 Kafka Client 的 send 方法时异步的,但 Kafka 故障或者 Kafka 生产者的内存满时,会 block主线程。虽然可以通过多线程的方式解决,但线程之间的频繁切换以及send方法里的同步锁势必会造成性能影响。

借助 RxJava 中的生产者-消费者模式,可以有效的解决这个问题:

java 复制代码
    Observable.<GatewayApiEntity>create(
        emitter -> {
          consumer = emitter::onNext;
          cleaner = emitter::onCompleted;
        })
        .onBackpressureBuffer(
            BUFFER_CAPACITY, // 经调试最终Capacity:BUFFER_CAPACITY+128(默认)
            () -> logger.info("Buffer is filling up now."),
            BackpressureOverflow.ON_OVERFLOW_DROP_OLDEST) // 当 Buffer 满时,Drop 旧值,并添加新值
        .filter(Objects::nonNull)
        .observeOn(Schedulers.io())//切换到异步线程消费
        .doOnCompleted(countDownLatch::countDown)
        .subscribe(this::sendMessage);

使用异步被压策略好处

  • Kafka 获取元数据或者当 buffer.memory >32 时,Kafka 生产者将阻塞 max.block.ms =60000 ms ,故不能将 Send 放到 Zuul IO 线程中
  • 通过生产者-消费者,将 Kafka 生产者 Send 方式并行转变为串行,减少多线程的同步、锁竞争等问题
  • 当 Kafka 故障、吞吐量降低时,背压的丢弃策略,可以防止 OOM

装饰者

目的

动态地给一个对象添加一些额外的功能,能在不影响原有功能的基础上,对其扩展功能。

场景 1

网关路由时,需要获取远程服务相关元数据,然后通过本地负载均衡选取具体的服务实例。默认情况下,NettyOriginManager 对象将远程的 Origin 缓存在内存中:ConcurrentHashMap。从功能上来看,这是没问题的。但为了性能上的优化,试想一下,当网关重启时,这些缓存数据将丢失,又需要重新去获取一遍元数据,下游服务越多,第一次请求的性能影响越大。如果在网关重启时,默认同步所有服务元数据下来,是不是会更好?所以,需要确定哪些服务要被初始化,这就需要在 createOrigin方法中额外增加这个保存Origin的逻辑。

OriginManager 的实现类 NettyOriginManager 支持对 Origin 的管理,创建和获取

java 复制代码
Slf4j
@Singleton
public class NettyOriginManager implements OriginManager<NettyOrigin>, Closeable {

    private final ConcurrentHashMap<OriginKey, NettyOrigin> originMappings = new ConcurrentHashMap<>();

    @Override
    public NettyOrigin getOrigin(String name, String vip, String uri, SessionContext ctx{
     
    }

    @Override
    public NettyOrigin createOrigin(String name, String vip, String uri, boolean useFullVipName, SessionContext ctx) {
       
    }
}

将元数据保存原本NettyOriginManager对象并不关心,同时如果NettyOriginManager有三方框架提供,是无法修改其源码。故使用装饰者模式,可以有效解决这个尴尬的问题,如下所示:在不侵入NettyOriginManager 的情况下,对其增强

java 复制代码
public interface OriginManagerDecorator extends OriginManager<NettyOrigin> {

    void init();

    void saveOrigin(String name, String vip, Map<String, Boolean> routingEntries);

    void deleteOrigin(String data);
}

以保存到 Redis 为例,新增装饰对象:RedissonOriginManager 装饰 NettyOriginManager,在原有能力上具备持久化的功能

java 复制代码
@Singleton
@Slf4j
public class RedissonOriginManager implements OriginManagerDecorator {

    @Inject
    private RedissonReactiveClient redissonClient;

    /*
    被装饰对象
     */
    @Inject

    private NettyOriginManager nettyOriginManager;

    @Override
    @PostConstruct
    public void init() {
        //获取redis namespace,初始化
    }

    @Override
    public void saveOrigin(String name, String vip, Map<String, Boolean> routingEntries){
    
    }

    @Override
    public void deleteOrigin(String data) {
        
    }

    @Override
    public NettyOrigin getOrigin(String name, String vip, String uri, SessionContext ctx{
        //pass through
        return nettyOriginManager.getOrigin(name, vip, uri, ctx);
    }

    @Override
    public NettyOrigin createOrigin(String name, String vip, String uri, boolean useFullVipName, SessionContext ctx) {
         //pass through
        NettyOrigin origin = nettyOriginManager.createOrigin(name, vip, uri, useFullVipName, ctx);
        //对原有的Origin Manager 进行增强,如果存在 Origin的话,对其缓存
        if (origin != null && origin instanceof SimpleNettyOrigin) {
            saveOrigin(name, vip, ((SimpleNettyOrigin) origin).getRouting().getRoutingEntries());
        }
        return origin;
    }
}

在原有功能上新增了持久化到 Redis 的功能,可以根据不同的场景,装饰不同的实现方式:Redis、数据库、配置中心等

场景 2

网关在处理请求时,默认情况下只打印关键信息到日志,但是有时为了排查错误,需要打印更加丰富的日志。这是一种动态功能的增强,以开关的形式启用,关闭。如下,默认情况下RequestEndingHandlerTraceIdElapseTime 返回到客户端:

typescript 复制代码
public class RequestEndingHandler implements RequestHandler {

  private Set<HeaderName> headers=new HashSet<>(
      Arrays.asList(HttpHeaderNames.get(Inbound.X_Tsign_Elapse_Time),
                    HttpHeaderNames.get(Inbound.X_Tsign_Trace_Id)));

  @Override
  public void handle(Object obj) {
      //一些服务先走应用网关,再走开放网关,清空下开放网关的响应头,使用应用网关的
      response.getHeaders().removeIf(headerEntry -> headers.contains(headerEntry.getKey()));
      //统计消耗的时间,放在响应头,便于排查问题
      response.getHeaders().add(Inbound.X_Tsign_Elapse_Time,
          String.valueOf(TimeUnit.MILLISECONDS.convert(request.getDuration(), TimeUnit.NANOSECONDS)));
      //trace-id,用于调用链跟踪
      //谨防 Null
      response.getHeaders().add(Inbound.X_Tsign_Trace_Id, StringUtils
          .defaultString(             response.getOutboundRequest().getHeaders().getFirst(CerberusConstants.TRACE_ID), ""));
  }
}

当开启了详细模式后,对原功能进行增强,支持所有的业务参数打印到日志:

java 复制代码
public class SessionContextLogHandler extends RequestLogHandleDecorator {

  private final static char DELIM = '\t';

  protected SessionContextLogHandler(
      RequestHandler handler) {
    super(handler);
  }

  @Override
  protected void log(Object obj) {
      StringBuilder sb=new StringBuilder();
      sb
          .append(DELIM).append(context.getOrDefault(CerberusConstants.TRACE_ID,"-"))
          .append(DELIM).append(context.getOrDefault(Inbound.X_TSIGN_LOGIN_ID,"-"))
          .append(DELIM).append(context.getOrDefault(OpenProtocol.USER_ID,"-"))
      ;
      logger.info(sb.toString());
  }

建造者

目的

将复杂对象构建与主业务流程分离

场景 1

网关支持将所有经过网关的 HTTP 日志记录在 Kafka 中,这个 Message 对象是个大对象,并且对于其中的 requestHeaderresponseBody 构建算法复杂。

通过构建者模式,将复杂对象从业务中剥离,避免过多的 if-else 造成混乱。

java 复制代码
private GatewayApiEntity construct(HttpResponseMessage response){
    entity = GatewayApiEntity.builder()
          .appId(request.getHeaders().getFirst(Inbound.X_TSIGN_APP_ID))
          .clientIp(HttpUtils.getClientIP(request))
          .method(request.getMethod())
          .requestId(context.getUUID())
          .serviceId(context.getRouteVIP())
          .api((String) context.getOrDefault(CerberusConstants.ORIGINAL_API, ""))
          .requestTime((Long) context.get(CerberusConstants.TIMING_START_CTX_KEY))
          .source(getApplicationId())
          .timestamp(System.currentTimeMillis())
          .traceId((String) context.getOrDefault(CerberusConstants.TRACE_ID, ""))
          .url(request.getInboundRequest().getPathAndQuery())
          .userAgent(request.getHeaders().getFirst(HttpHeaders.USER_AGENT))
          .status(response.getStatus())
          .duration(getDuration(response))
          .requestHeader(getRequestHeader(request))
          .requestBody(getRequestBody(request))
          .responseBody(getResponseBody(response))
          .build();
}

  private String getRequestHeader(HttpRequestMessage request) throws JsonProcessingException {
    // 3.补充请求头 X-Tsign
  }

  private String getRequestBody(HttpRequestMessage request){
    //4.请求数据,如果是大包的话,不进行收集,因为 Broker 端对 Producer 发送过来的消息也有一定的大小限制,这个参数叫 message.max.bytes
  }

  private String getResponseBody(HttpResponseMessage response) throws IOException {
    // 5.处理 Body 里的数据,如果是大包的话,不进行收集,因为 Broker 端对 Producer 发送过来的消息也有一定的大小限制,这个参数叫 message.max.bytes
 	// Response body 被 gzip 压缩过
  }

场景 2

网关核心功能即路由,比如对请求: v1/accounts/abcdefg/infos 路由到 v1/accounts/{accountId}/infos 后端接口上 ,所以这需要正则表达式的支持。网关通过建造者模式,构建出一个复杂的 API 对象来表示元数据。

java 复制代码
 Api.builder()
     .serviceId(entity.getServiceId())
     .url(url)
     .originalUrl(StringUtils.prependIfMissing(entity.originalUrl, "/"))
     .httpMethod(entity.getHttpMethod())
     .readTimeout(readTimeout)
     .uriTemplate(uriTemplateFactory.create(url))
     .build();

写在最后

欢迎关注我的公众号:编程启示录,第一时间获取最新消息;

微信 公众号
相关推荐
秦朝胖子得加钱16 分钟前
Flask
后端·python·flask
uzong19 分钟前
JDK高性能套路: 自旋(for(;;)) + CAS
java·后端
程序员yt43 分钟前
2025秋招八股文--服务器篇
linux·运维·服务器·c++·后端·面试
程序员苏桑1 小时前
从实际项目说代码重构
后端
web_code1 小时前
vite依赖预构建(源码分析)
前端·面试·vite
DeviceArtist1 小时前
我穿越回2013年,拿到一台旧电脑,只为给Android2.3设备写一个时钟程序
后端
机器之心1 小时前
Runway CEO:AI公司的时代已经结束了
人工智能·后端
IT·小灰灰2 小时前
Python——自动化发送邮件
运维·网络·后端·python·自动化
颜淡慕潇2 小时前
【K8S系列】Kubernetes 中 Service IP 分配 问题及解决方案【已解决】
后端·云原生·容器·kubernetes
秋恬意3 小时前
LinkedList 源码分析
java·开发语言·面试