设计模式生成

338 浏览
30 试用
8 购买
Nov 24, 2025更新

本提示词可根据指定系统类型和编程语言,生成设计模式的实现方案,并附清晰代码示例,解释该模式如何解决实际问题,帮助开发者快速应用设计模式于系统架构和组件开发中。

设计模式原理说明

观察者模式(Observer Pattern)通过建立“被观察者(Subject)—观察者(Observer)”的一对多依赖关系,使得当被观察者状态发生变化时,所有依赖它的观察者都会得到通知并自动更新。核心要点:

  • 解耦:观察者不直接依赖生产者,只依赖统一的 Subject;Subject 也不关心具体观察者是谁。
  • 可扩展:可以动态添加/移除观察者,支持一次性订阅、优先级分发、按主题订阅等。
  • 可靠性:通过缓冲、重放和自动退订控制生命周期,减少内存泄漏与事件丢失。

在电商后台 SPA 中,多个组件(订单列表、库存预警、客服通知、导航徽标等)需要消费来自 WebSocket 与轮询的混合事件。若每个组件各自维护连接与监听,将导致:

  • 重复连接、重复解析、强耦合;
  • 页面切换时监听未清理,造成内存泄漏;
  • 断线重连期间事件易丢失或乱序。

观察者模式的统一事件中心作为唯一 Subject,标准化事件输入(生产者 -> Subject)与输出(Subject -> 观察者),并通过主题与优先级控制分发,结合自动退订和缓存,解决上述问题。


解决方案分析

目标与要点:

  • 统一事件中心(Subject):按主题订阅/退订(order.created、order.updated、stock.low、chat.new),支持一次性订阅(once)、优先级分发(priority)、批量退订。
  • 生命周期管理:组件卸载时自动取消订阅(AbortSignal/Controller),杜绝内存泄漏。
  • 断线重连:指数退避 + 抖动重连;WebSocket 断开时启用轮询兜底。
  • 事件缓存与重放:中心按主题维持环形缓冲,订阅时可选择重放最近 N 条,避免组件卸载/重挂期间丢事件。
  • 多观察者安全消费:同一事件可被多个观察者并行消费,单个观察者异常不影响其他观察者。

架构分层:

  • EventCenter(Subject):主题 -> 订阅者表;提供 subscribe/unsubscribe/publish/batchUnsubscribe;内建 per-topic 环形缓冲与优先级分发。
  • RealtimeBridge(生产者适配):统一 WebSocket 与轮询,断线时指数退避重连;轮询根据游标拉取增量;统一产出规范化事件 -> EventCenter.publish。
  • 组件侧:使用 ComponentScope(提供 AbortController),在 mount() 时订阅,unmount() 时自动取消;展示订单列表与通知条组件订阅、一次性订阅、批量退订与优先级效果。

可靠性策略:

  • 指数退避重连:delay = min(maxDelay, base * 2^attempt) + jitter,避免雪崩。
  • 轮询兜底:WS 断开即启动轮询,按最后序号游标增量拉取。
  • 环形缓冲重放:新订阅可选择 replayLatestN,组件卸载/重挂不丢关键信息。
  • 安全分发:按优先级排序;单个观察者异常被捕获并隔离;一次性订阅在首次触发后移除。

代码示例(JavaScript,浏览器/Node 18+ 可运行)

说明:

  • 完整示例包含 EventCenter、RealtimeBridge(含 Mock 数据源以便直接运行演示)、组件示例(订单列表与通知条)。
  • 重点标注关键逻辑:主题订阅、一次性订阅、优先级分发、批量退订、自动退订、断线重连、轮询兜底与事件重放。
// ========== 工具函数 ==========
function genId() {
  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}

function sleep(ms) {
  return new Promise((res) => setTimeout(res, ms));
}

// ========== 统一事件中心(Subject / Observer)==========
class EventCenter {
  constructor({ bufferSizePerTopic = 50 } = {}) {
    this.subscribers = new Map(); // topic -> [{ id, handler, once, priority, signal, addedAt }]
    this.buffers = new Map();     // topic -> ring buffer array [{ data, ts }]
    this.bufferSizePerTopic = bufferSizePerTopic;
  }

  // 内部:确保 Map 初始化
  ensureTopic(topic) {
    if (!this.subscribers.has(topic)) this.subscribers.set(topic, []);
    if (!this.buffers.has(topic)) this.buffers.set(topic, []);
  }

  // 发布事件:按优先级分发,支持 once,异常隔离
  publish(topic, data) {
    this.ensureTopic(topic);
    // 写入环形缓冲
    const buf = this.buffers.get(topic);
    buf.push({ data, ts: Date.now() });
    if (buf.length > this.bufferSizePerTopic) buf.shift();

    // 复制一份订阅者列表,避免分发中被修改影响
    const subs = [...this.subscribers.get(topic)];
    // 按 priority 降序,priority 相同按添加时间先后
    subs.sort((a, b) => {
      if (b.priority !== a.priority) return b.priority - a.priority;
      return a.addedAt - b.addedAt;
    });

    let delivered = 0;
    for (const sub of subs) {
      if (!this.hasSubscriber(topic, sub.id)) continue; // 可能被前面 once 移除
      try {
        sub.handler(data);
        delivered++;
      } catch (err) {
        // 隔离观察者异常
        console.error(`[EventCenter] handler error on topic "${topic}":`, err);
      }
      if (sub.once) {
        this.unsubscribe({ topic, id: sub.id });
      }
    }
    return delivered;
  }

  hasSubscriber(topic, id) {
    const list = this.subscribers.get(topic) || [];
    return list.some((s) => s.id === id);
  }

  // 订阅:支持 once、priority、AbortSignal 自动退订、重放最近 N 条
  subscribe(topic, handler, options = {}) {
    const { once = false, priority = 0, signal = undefined, replayLatestN = 0 } = options;
    this.ensureTopic(topic);
    const id = genId();
    const entry = { id, handler, once, priority, signal, addedAt: Date.now() };
    this.subscribers.get(topic).push(entry);

    // 自动退订:监听 AbortSignal
    let abortListener = null;
    if (signal) {
      abortListener = () => this.unsubscribe({ topic, id });
      signal.addEventListener('abort', abortListener, { once: true });
    }

    // 重放最近 N 条
    if (replayLatestN > 0) {
      const buf = this.buffers.get(topic);
      const recent = buf.slice(-replayLatestN);
      for (const e of recent) {
        try {
          handler(e.data);
          if (once) {
            // 重放已触发一次性订阅,立即移除
            this.unsubscribe({ topic, id });
            break;
          }
        } catch (err) {
          console.error(`[EventCenter] replay handler error on "${topic}":`, err);
        }
      }
    }

    // 返回可退订对象
    const unsubscribe = () => {
      this.unsubscribe({ topic, id });
      if (signal && abortListener) {
        try {
          signal.removeEventListener('abort', abortListener);
        } catch (_) {}
      }
    };
    return { topic, id, unsubscribe };
  }

  subscribeOnce(topic, handler, options = {}) {
    return this.subscribe(topic, handler, { ...options, once: true });
  }

  unsubscribe(token) {
    const { topic, id } = token;
    const list = this.subscribers.get(topic);
    if (!list) return;
    const idx = list.findIndex((s) => s.id === id);
    if (idx >= 0) list.splice(idx, 1);
  }

  // 批量退订:接受 token 数组或过滤器
  batchUnsubscribe(arg) {
    if (Array.isArray(arg)) {
      for (const t of arg) this.unsubscribe(t);
      return;
    }
    const filter = arg; // (topic, subscriber) => boolean,返回 true 表示应退订
    for (const [topic, list] of this.subscribers.entries()) {
      for (const s of [...list]) {
        if (filter(topic, s)) {
          this.unsubscribe({ topic, id: s.id });
        }
      }
    }
  }
}

// ========== 模拟事件源(用于演示):统一为 WS 与轮询提供数据 ==========
class MockEventFeed {
  constructor() {
    this.seq = 0;
    this.events = []; // 存储所有事件供轮询拉取
    this.wsConsumers = new Set(); // 已连接的 WS 回调
    this.produceTimer = null;
  }

  // 随机生成事件
  randomEvent() {
    const topics = ['order.created', 'order.updated', 'stock.low', 'chat.new'];
    const topic = topics[Math.floor(Math.random() * topics.length)];
    const payloads = {
      'order.created': { orderId: `O-${this.seq}`, user: `U${Math.ceil(Math.random()*100)}` },
      'order.updated': { orderId: `O-${Math.max(1, this.seq - 1)}`, status: ['PAID','SHIPPED','CANCELLED'][Math.floor(Math.random()*3)] },
      'stock.low':     { sku: `SKU-${Math.ceil(Math.random()*10)}`, remain: Math.ceil(Math.random()*3) },
      'chat.new':      { from: ['Alice','Bob','Carol'][Math.floor(Math.random()*3)], text: 'New message' },
    };
    return { topic, data: payloads[topic], ts: Date.now(), seq: ++this.seq };
  }

  // 周期性产出事件
  startProducing(intervalMs = 800) {
    if (this.produceTimer) return;
    this.produceTimer = setInterval(() => {
      const evt = this.randomEvent();
      this.events.push(evt);
      // 推送给所有“已连接的 WS”
      for (const cb of this.wsConsumers) {
        try { cb(evt); } catch (e) { console.error('WS consumer error', e); }
      }
    }, intervalMs);
  }

  stopProducing() {
    if (this.produceTimer) {
      clearInterval(this.produceTimer);
      this.produceTimer = null;
    }
  }

  // WS 订阅(模拟 onmessage 注册)
  connectWS(onEvent) {
    this.wsConsumers.add(onEvent);
    return () => this.wsConsumers.delete(onEvent); // 返回断开函数
  }

  // 轮询增量接口:获取 seqAfter 之后的事件
  fetchSince(seqAfter, limit = 50) {
    const idx = this.events.findIndex((e) => e.seq > seqAfter);
    if (idx === -1) return [];
    return this.events.slice(idx, idx + limit);
  }
}

// ========== 断线重连桥接:优先 WS,断线时启用轮询 ==========
class RealtimeBridge {
  constructor({ eventCenter, feed, backoff = { base: 500, max: 8000, jitter: 200 }, pollInterval = 1200 }) {
    this.eventCenter = eventCenter;
    this.feed = feed; // MockEventFeed(演示)。生产环境请替换为真实 WS 与 HTTP Poller
    this.backoff = backoff;
    this.pollInterval = pollInterval;

    this.connected = false;
    this.wsDisconnectFn = null;
    this.reconnectAttempt = 0;

    this.pollTimer = null;
    this.lastSeq = 0; // 最近处理到的事件序号
  }

  start() {
    this.connectWS();
  }

  stop() {
    this.disconnectWS();
    this.stopPolling();
  }

  // 连接 WS:连接成功停止轮询;断开后触发重连
  connectWS() {
    if (this.connected) return;
    // 模拟建立 WS 连接
    this.wsDisconnectFn = this.feed.connectWS((evt) => {
      this.onIncomingEvent(evt);
    });
    this.connected = true;
    this.reconnectAttempt = 0;
    this.stopPolling();
    // 模拟外部随机断网:仅用于演示
    this.simulateRandomDrop();
    // 控制台提示
    console.log('[Bridge] WS connected');
  }

  disconnectWS() {
    if (this.wsDisconnectFn) {
      this.wsDisconnectFn();
      this.wsDisconnectFn = null;
    }
    if (this.connected) {
      this.connected = false;
      console.warn('[Bridge] WS disconnected');
    }
  }

  // 指数退避重连
  async scheduleReconnect() {
    this.disconnectWS();
    this.startPolling(); // WS 不可用,启用轮询兜底
    this.reconnectAttempt++;
    const { base, max, jitter } = this.backoff;
    const delay = Math.min(max, base * Math.pow(2, this.reconnectAttempt - 1)) + Math.floor(Math.random() * jitter);
    console.warn(`[Bridge] Reconnecting in ~${delay}ms (attempt ${this.reconnectAttempt})`);
    await sleep(delay);
    // 尝试重连
    try {
      this.connectWS();
    } catch (e) {
      console.error('[Bridge] reconnect error', e);
      this.scheduleReconnect(); // 继续重试
    }
  }

  startPolling() {
    if (this.pollTimer) return;
    this.pollTimer = setInterval(() => {
      const events = this.feed.fetchSince(this.lastSeq, 50);
      for (const evt of events) {
        this.onIncomingEvent(evt);
      }
    }, this.pollInterval);
    console.log('[Bridge] Polling started');
  }

  stopPolling() {
    if (this.pollTimer) {
      clearInterval(this.pollTimer);
      this.pollTimer = null;
      console.log('[Bridge] Polling stopped');
    }
  }

  onIncomingEvent(evt) {
    // 去重/顺序控制:忽略小于等于 lastSeq 的事件
    if (evt.seq <= this.lastSeq) return;
    this.lastSeq = evt.seq;
    // 统一入中心
    this.eventCenter.publish(evt.topic, { ...evt.data, _meta: { ts: evt.ts, seq: evt.seq, via: this.connected ? 'ws' : 'poll' } });
  }

  // 演示:随机模拟在 5~10s 内断一次网
  simulateRandomDrop() {
    const ms = 5000 + Math.floor(Math.random() * 5000);
    setTimeout(() => {
      if (!this.connected) return;
      this.disconnectWS();
      this.scheduleReconnect();
    }, ms);
  }
}

// ========== 组件生命周期封装:自动退订 ==========
class ComponentScope {
  constructor(name = 'Component') {
    this.name = name;
    this.controller = new AbortController(); // 用于自动退订
    this.tokens = []; // 手工批量退订示例
    this.mounted = false;
  }
  mount() {
    this.mounted = true;
    console.log(`[${this.name}] mounted`);
  }
  unmount() {
    if (!this.mounted) return;
    this.controller.abort(); // 触发自动退订
    this.tokens = []; // 清空手工 token
    this.mounted = false;
    console.log(`[${this.name}] unmounted (auto-unsubscribed)`);
  }
  signal() {
    return this.controller.signal;
  }
  holdToken(token) {
    this.tokens.push(token);
    return token;
  }
  batchUnsubscribeHeld() {
    for (const t of this.tokens) t.unsubscribe();
    this.tokens = [];
    console.log(`[${this.name}] batch unsubscribed held tokens`);
  }
}

// ========== 具体组件示例:订单列表与通知条 ==========
class OrderList extends ComponentScope {
  constructor(eventCenter) {
    super('OrderList');
    this.eventCenter = eventCenter;
    this.orders = new Map();
  }
  mount() {
    super.mount();
    // 订阅 order.created(高优先级,确保先更新列表再交给其它观察者展示)
    this.holdToken(
      this.eventCenter.subscribe('order.created', (e) => {
        this.orders.set(e.orderId, { ...e, status: 'CREATED' });
        console.log(`[OrderList] order.created -> add ${e.orderId} (via ${e._meta.via})`);
      }, { priority: 10, signal: this.signal(), replayLatestN: 3 }) // 重放最近 3 条,避免错过
    );

    // 订阅 order.updated(同一主题可以被多个观察者订阅;演示优先级较低)
    this.holdToken(
      this.eventCenter.subscribe('order.updated', (e) => {
        const prev = this.orders.get(e.orderId) || {};
        this.orders.set(e.orderId, { ...prev, ...e });
        console.log(`[OrderList] order.updated -> ${e.orderId} = ${e.status} (via ${e._meta.via})`);
      }, { priority: 5, signal: this.signal(), replayLatestN: 5 })
    );

    // 一次性订阅:只在页面首次挂载后收到的第一条新订单时提示
    this.holdToken(
      this.eventCenter.subscribeOnce('order.created', (e) => {
        console.log(`[OrderList] once -> first order observed after mount: ${e.orderId}`);
      }, { priority: 100, signal: this.signal() })
    );
  }
}

class NotificationBar extends ComponentScope {
  constructor(eventCenter) {
    super('NotificationBar');
    this.eventCenter = eventCenter;
  }
  mount() {
    super.mount();

    // 库存预警
    this.holdToken(
      this.eventCenter.subscribe('stock.low', (e) => {
        console.log(`[NotificationBar] stock.low -> SKU ${e.sku} remain ${e.remain} (via ${e._meta.via})`);
      }, { priority: 50, signal: this.signal(), replayLatestN: 2 })
    );

    // 客服消息
    this.holdToken(
      this.eventCenter.subscribe('chat.new', (e) => {
        console.log(`[NotificationBar] chat.new -> from ${e.from}: ${e.text} (via ${e._meta.via})`);
      }, { priority: 50, signal: this.signal() })
    );

    // 订阅同一事件(order.created),验证多观察者安全消费 + 优先级次序不同于 OrderList
    this.holdToken(
      this.eventCenter.subscribe('order.created', (e) => {
        console.log(`[NotificationBar] order.created badge ++ for ${e.orderId}`);
      }, { priority: 1, signal: this.signal() }) // 优先级低于 OrderList
    );

    // 一次性订阅:仅提示“首次订单已产生”
    this.holdToken(
      this.eventCenter.subscribeOnce('order.created', (e) => {
        console.log(`[NotificationBar] once -> first order badge highlight: ${e.orderId}`);
      }, { priority: 60, signal: this.signal() })
    );
  }
}

// ========== 演示主流程 ==========
async function main() {
  console.log('==== Demo Start: Observer-based unified event center ====');

  // 1) 启动统一事件中心(主题缓冲默认 50)
  const center = new EventCenter({ bufferSizePerTopic: 50 });

  // 2) 启动事件源(Mock),与桥接(WS+轮询+重连)
  const feed = new MockEventFeed();
  feed.startProducing(400); // 更高频率,便于演示
  const bridge = new RealtimeBridge({
    eventCenter: center,
    feed,
    backoff: { base: 500, max: 5000, jitter: 300 },
    pollInterval: 700,
  });
  bridge.start();

  // 3) 组件挂载
  const orderList = new OrderList(center);
  const notifyBar = new NotificationBar(center);
  orderList.mount();
  notifyBar.mount();

  // 4) 演示:在 6 秒后批量退订 NotificationBar 的所有持有 token(即使不卸载,也可批量退订一组订阅)
  setTimeout(() => {
    notifyBar.batchUnsubscribeHeld();
  }, 6000);

  // 5) 再过 4 秒卸载 NotificationBar(自动退订)
  setTimeout(() => {
    notifyBar.unmount();
  }, 10000);

  // 6) 继续运行一段时间后卸载 OrderList,结束演示
  setTimeout(() => {
    orderList.unmount();
    bridge.stop();
    feed.stopProducing();
    console.log('==== Demo End ====');
  }, 15000);
}

// 运行演示
main();

关键点回顾:

  • 按主题订阅/退订:EventCenter.subscribe/unsubscribe,支持一次性订阅(subscribeOnce)。
  • 优先级分发:publish 内部对订阅者按 priority 降序排序。
  • 批量退订:ComponentScope.holdToken + batchUnsubscribeHeld,或 EventCenter.batchUnsubscribe(filter)。
  • 自动取消订阅:订阅时传入 AbortSignal(组件的 AbortController.signal),组件 unmount() 时自动触发退订。
  • 断线重连与轮询兜底:RealtimeBridge 在 WS 断开后启用轮询,同时指数退避重连;重连成功停止轮询。
  • 事件缓存与重放:EventCenter 维持 per-topic 环形缓冲,订阅时可设 replayLatestN 立即重放最近 N 条,避免组件卸载/重挂期间丢事件。
  • 多观察者安全消费:同一事件(order.created)由 OrderList 与 NotificationBar 同时订阅,分发时独立调用,异常隔离且不影响他方。

将本示例替换为真实后端时:

  • 用真实 WebSocket 替换 MockEventFeed.connectWS。
  • 轮询 fetchSince(seq) 替换为实际 HTTP 接口。
  • 保留 EventCenter API 与 RealtimeBridge 行为不变,即可在生产中复用。

以下方案基于责任链模式(Chain of Responsibility)为 Spring Boot API 网关构建可插拔、可配置、可扩展的请求处理链,满足“多步骤预处理、动态顺序与开关、短路返回统一错误体、异常捕获、同步与异步处理、链路追踪”等要求。


设计模式原理说明

  • 核心思想:将请求处理拆分为一系列职责明确的处理器(Handler),每个处理器决定是否处理及是否将请求传递给下一个处理器。通过解耦请求与处理逻辑的绑定,支持动态组合与扩展。
  • 解决的问题:
    • 处理步骤(鉴权、限流、灰度、审计等)分散在拦截器/过滤器中,顺序难以调整、开关难以控制。
    • 新增/禁用步骤需改代码、重新部署。
    • 某些步骤需要短路(例如鉴权失败、限流),并返回统一错误响应。
    • 需要捕获异常并封装统一响应。
    • 支持同步与异步处理(如审计日志异步落盘)、并贯穿 traceId 进行链路追踪。
  • 适用场景:
    • API 网关的前置校验与治理。
    • 微服务入口统一安全与治理控制。
    • 可按配置灵活编排的多步骤处理流程。

解决方案分析

  • 统一上下文 RequestContext:贯穿整个链条,承载 Http 请求、响应、traceId、租户、用户、灰度标签、属性容器等。
  • Handler 接口:抽象单个处理步骤,支持短路返回与继续传递。通过 HandlerChain 控制调用下一个 Handler。
  • HandlerChain:按配置顺序执行处理器列表,处理器可以通过返回 HandlerResult 停止后续处理(短路)。
  • 动态装配:通过配置文件(YAML)声明处理器 bean 名、启用开关与顺序,启动时装配成处理链(支持扩展为动态刷新)。
  • 统一响应包装:错误时使用统一 ApiResponse 包装,包含 code、message、traceId;正常流保持原控制器输出或也可统一包装(可选)。
  • 异常捕获:网关 Filter 统一 try/catch,捕获并包装为统一错误响应,且触发审计回调。
  • 同步与异步处理:
    • 同步:多数校验(鉴权、限流、灰度)为同步执行。
    • 异步:审计日志采用回调 + 线程池异步执行,不阻塞请求链。
  • 链路追踪 traceId:从请求头读取 X-Trace-Id 或生成 UUID,贯穿在 RequestContext、日志 MDC、下游请求头及响应头中。
  • 示例处理器实现:
    • JwtAuthHandler:校验 JWT,失败短路返回 AUTH_FAILED。
    • RateLimitHandler:基于租户或用户的简单 QPS 限流,超过短路返回 RATE_LIMITED。
    • GrayReleaseHandler:根据百分比/标记进行灰度标签计算,写入上下文与请求头传递。
    • AuditHandler:注册完成回调,在请求完成后异步写审计日志(记录 code、耗时、用户、租户、traceId 等)。

代码示例

说明:

  • 包名:com.example.gateway
  • 构建工具:Maven(省略 pom.xml,需包含 spring-boot-starter-web、spring-boot-starter-test、lombok、jackson)
  • 使用 OncePerRequestFilter 作为网关入口
  • 配置文件控制链顺序与是否启用
  • 提供单元测试展示短路、限流、traceId 贯穿
  1. 通用模型与基础设施
// src/main/java/com/example/gateway/common/ApiResponse.java
package com.example.gateway.common;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> {
    private int code;        // 0 表示成功
    private String message;  // 业务描述
    private String traceId;  // 链路ID
    private T data;

    public ApiResponse() {}

    public ApiResponse(int code, String message, String traceId, T data) {
        this.code = code;
        this.message = message;
        this.traceId = traceId;
        this.data = data;
    }

    public static <T> ApiResponse<T> success(T data, String traceId) {
        return new ApiResponse<>(0, "OK", traceId, data);
    }

    public static <T> ApiResponse<T> error(ErrorCode code, String message, String traceId) {
        return new ApiResponse<>(code.getCode(), message, traceId, null);
    }

    public static <T> ApiResponse<T> error(ErrorCode code, String traceId) {
        return new ApiResponse<>(code.getCode(), code.getDefaultMessage(), traceId, null);
    }

    // getters/setters
    public int getCode() { return code; }
    public void setCode(int code) { this.code = code; }
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    public String getTraceId() { return traceId; }
    public void setTraceId(String traceId) { this.traceId = traceId; }
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
}
// src/main/java/com/example/gateway/common/ErrorCode.java
package com.example.gateway.common;

public enum ErrorCode {
    SUCCESS(0, "OK"),
    AUTH_FAILED(1001, "Authentication failed"),
    RATE_LIMITED(1002, "Too many requests"),
    FORBIDDEN_IP(1003, "IP not allowed"),
    TENANT_INVALID(1004, "Invalid tenant"),
    INTERNAL_ERROR(2000, "Internal error");

    private final int code;
    private final String defaultMessage;

    ErrorCode(int code, String defaultMessage) {
        this.code = code;
        this.defaultMessage = defaultMessage;
    }

    public int getCode() { return code; }
    public String getDefaultMessage() { return defaultMessage; }
}
// src/main/java/com/example/gateway/common/GatewayException.java
package com.example.gateway.common;

public class GatewayException extends RuntimeException {
    private final ErrorCode code;

    public GatewayException(ErrorCode code, String message) {
        super(message);
        this.code = code;
    }

    public GatewayException(ErrorCode code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

    public ErrorCode getCode() {
        return code;
    }
}
// src/main/java/com/example/gateway/core/RequestContext.java
package com.example.gateway.core;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;

public class RequestContext {
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final Map<String, Object> attributes = new ConcurrentHashMap<>();
    private final List<Consumer<RequestContext>> completionCallbacks = new CopyOnWriteArrayList<>();

    private String traceId;
    private String tenantId;
    private String userId;
    private String grayTag;
    private long startTimeMs = System.currentTimeMillis();

    public RequestContext(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
    }

    public HttpServletRequest getRequest() { return request; }
    public HttpServletResponse getResponse() { return response; }

    public String getTraceId() { return traceId; }
    public void setTraceId(String traceId) { this.traceId = traceId; }

    public String getTenantId() { return tenantId; }
    public void setTenantId(String tenantId) { this.tenantId = tenantId; }

    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }

    public String getGrayTag() { return grayTag; }
    public void setGrayTag(String grayTag) { this.grayTag = grayTag; }

    public long getStartTimeMs() { return startTimeMs; }

    public void putAttr(String key, Object val) { attributes.put(key, val); }
    @SuppressWarnings("unchecked")
    public <T> T getAttr(String key) { return (T) attributes.get(key); }

    public void addCompletionCallback(Consumer<RequestContext> callback) {
        completionCallbacks.add(callback);
    }

    public void runCompletionCallbacks() {
        for (Consumer<RequestContext> cb : completionCallbacks) {
            try { cb.accept(this); } catch (Exception ignored) {}
        }
    }
}
// src/main/java/com/example/gateway/core/Handler.java
package com.example.gateway.core;

public interface Handler {
    String name();

    // 返回 HandlerResult。若需要继续执行后续处理器,调用 chain.proceed(ctx)。
    HandlerResult handle(RequestContext ctx, HandlerChain chain) throws Exception;

    // 可根据上下文决定是否执行(例如租户维度开关)
    default boolean supports(RequestContext ctx) { return true; }

    // 标记该处理器是否包含异步逻辑(不影响责任链同步推进,仅用于文档/监控)
    default boolean isAsync() { return false; }

    // 用于默认排序(实际以配置为准)
    default int order() { return 0; }
}
// src/main/java/com/example/gateway/core/HandlerChain.java
package com.example.gateway.core;

public interface HandlerChain {
    HandlerResult proceed(RequestContext ctx) throws Exception;
}
// src/main/java/com/example/gateway/core/HandlerResult.java
package com.example.gateway.core;

import com.example.gateway.common.ApiResponse;

public class HandlerResult {
    private final boolean shortCircuited;
    private final ApiResponse<?> errorBody;

    private HandlerResult(boolean shortCircuited, ApiResponse<?> errorBody) {
        this.shortCircuited = shortCircuited;
        this.errorBody = errorBody;
    }

    public static HandlerResult ok() {
        return new HandlerResult(false, null);
    }

    public static HandlerResult stop(ApiResponse<?> errorBody) {
        return new HandlerResult(true, errorBody);
    }

    public boolean isShortCircuited() { return shortCircuited; }
    public ApiResponse<?> getErrorBody() { return errorBody; }
}
// src/main/java/com/example/gateway/core/DefaultHandlerChain.java
package com.example.gateway.core;

import java.util.List;

public class DefaultHandlerChain implements HandlerChain {
    private final List<Handler> handlers;
    private final int index;

    public DefaultHandlerChain(List<Handler> handlers) {
        this(handlers, 0);
    }
    private DefaultHandlerChain(List<Handler> handlers, int index) {
        this.handlers = handlers;
        this.index = index;
    }

    @Override
    public HandlerResult proceed(RequestContext ctx) throws Exception {
        if (index >= handlers.size()) {
            return HandlerResult.ok();
        }
        Handler current = handlers.get(index);
        if (!current.supports(ctx)) {
            return new DefaultHandlerChain(handlers, index + 1).proceed(ctx);
        }
        return current.handle(ctx, new DefaultHandlerChain(handlers, index + 1));
    }
}
  1. 配置与装配
// src/main/java/com/example/gateway/config/GatewayChainProperties.java
package com.example.gateway.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

@ConfigurationProperties(prefix = "gateway.chain")
public class GatewayChainProperties {
    private List<Step> steps = new ArrayList<>();

    public List<Step> getSteps() { return steps; }
    public void setSteps(List<Step> steps) { this.steps = steps; }

    public static class Step {
        private String bean;     // Spring Bean 名称
        private boolean enabled = true;
        private Integer order = 0;

        public String getBean() { return bean; }
        public void setBean(String bean) { this.bean = bean; }
        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        public Integer getOrder() { return order; }
        public void setOrder(Integer order) { this.order = order; }
    }
}
// src/main/java/com/example/gateway/config/RateLimitProperties.java
package com.example.gateway.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "gateway.ratelimit")
public class RateLimitProperties {
    private int qps = 50; // 默认每秒50

    public int getQps() { return qps; }
    public void setQps(int qps) { this.qps = qps; }
}
// src/main/java/com/example/gateway/config/GrayProperties.java
package com.example.gateway.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "gateway.gray")
public class GrayProperties {
    private int percent = 0; // 0-100

    public int getPercent() { return percent; }
    public void setPercent(int percent) { this.percent = percent; }
}
// src/main/java/com/example/gateway/config/ChainBuilder.java
package com.example.gateway.config;

import com.example.gateway.core.Handler;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Component
public class ChainBuilder {
    private final ApplicationContext ctx;
    private final GatewayChainProperties props;

    public ChainBuilder(ApplicationContext ctx, GatewayChainProperties props) {
        this.ctx = ctx;
        this.props = props;
    }

    // 按配置装配处理链
    public List<Handler> buildHandlers() {
        return props.getSteps().stream()
                .filter(GatewayChainProperties.Step::isEnabled)
                .sorted(Comparator.comparingInt(s -> s.getOrder() == null ? 0 : s.getOrder()))
                .map(step -> {
                    try {
                        Handler h = (Handler) ctx.getBean(step.getBean());
                        return Objects.requireNonNull(h, "Handler bean not found: " + step.getBean());
                    } catch (BeansException ex) {
                        throw new IllegalStateException("Cannot load handler bean: " + step.getBean(), ex);
                    }
                })
                .collect(Collectors.toList());
    }
}
// src/main/java/com/example/gateway/config/AppConfig.java
package com.example.gateway.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.MDC;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableConfigurationProperties({
        GatewayChainProperties.class,
        RateLimitProperties.class,
        GrayProperties.class
})
public class AppConfig {
    @Bean
    public ObjectMapper objectMapper() { return new ObjectMapper(); }

    // 审计异步线程池
    @Bean("auditExecutor")
    public Executor auditExecutor() {
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(2);
        exec.setMaxPoolSize(8);
        exec.setQueueCapacity(1000);
        exec.setThreadNamePrefix("audit-");
        exec.initialize();
        return r -> {
            // 透传 MDC 中的 traceId
            String traceId = MDC.get("traceId");
            exec.execute(() -> {
                if (traceId != null) MDC.put("traceId", traceId);
                try { r.run(); } finally { if (traceId != null) MDC.remove("traceId"); }
            });
        };
    }
}
  1. 处理器实现
// src/main/java/com/example/gateway/handler/JwtService.java
package com.example.gateway.handler;

import org.springframework.stereotype.Service;

// 示例 JWT 校验服务(生产中应使用标准库与签名公钥校验)
@Service
public class JwtService {
    public boolean validate(String token) {
        return "valid-token".equals(token);
    }
    public String parseUserId(String token) {
        return "valid-token".equals(token) ? "user-001" : null;
    }
}
// src/main/java/com/example/gateway/handler/JwtAuthHandler.java
package com.example.gateway.handler;

import com.example.gateway.common.ApiResponse;
import com.example.gateway.common.ErrorCode;
import com.example.gateway.core.*;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;

@Component("jwtAuthHandler")
public class JwtAuthHandler implements Handler {
    private final JwtService jwtService;

    public JwtAuthHandler(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @Override
    public String name() { return "JwtAuthHandler"; }

    @Override
    public HandlerResult handle(RequestContext ctx, HandlerChain chain) throws Exception {
        HttpServletRequest req = ctx.getRequest();
        String traceId = ctx.getTraceId();

        String auth = req.getHeader("Authorization");
        String tenantId = req.getHeader("X-Tenant-Id");

        if (tenantId == null || tenantId.isBlank()) {
            return HandlerResult.stop(ApiResponse.error(ErrorCode.TENANT_INVALID, "Missing X-Tenant-Id", traceId));
        }

        if (auth == null || !auth.startsWith("Bearer ")) {
            return HandlerResult.stop(ApiResponse.error(ErrorCode.AUTH_FAILED, "Missing or invalid Authorization header", traceId));
        }

        String token = auth.substring("Bearer ".length());
        if (!jwtService.validate(token)) {
            return HandlerResult.stop(ApiResponse.error(ErrorCode.AUTH_FAILED, "Token invalid", traceId));
        }

        String userId = jwtService.parseUserId(token);
        if (userId == null) {
            return HandlerResult.stop(ApiResponse.error(ErrorCode.AUTH_FAILED, "Token parse failed", traceId));
        }

        // 写入上下文
        ctx.setTenantId(tenantId);
        ctx.setUserId(userId);

        return chain.proceed(ctx);
    }
}
// src/main/java/com/example/gateway/handler/RateLimitHandler.java
package com.example.gateway.handler;

import com.example.gateway.common.ApiResponse;
import com.example.gateway.common.ErrorCode;
import com.example.gateway.core.*;

import com.example.gateway.config.RateLimitProperties;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

// 简单每秒计数器限流(按租户或用户),生产环境建议使用 Redis/令牌桶
@Component("rateLimitHandler")
public class RateLimitHandler implements Handler {
    private final RateLimitProperties props;

    // key -> window state
    private final Map<String, Window> counters = new ConcurrentHashMap<>();

    public RateLimitHandler(RateLimitProperties props) {
        this.props = props;
    }

    @Override
    public String name() { return "RateLimitHandler"; }

    @Override
    public HandlerResult handle(RequestContext ctx, HandlerChain chain) throws Exception {
        String traceId = ctx.getTraceId();
        String key = "t:" + ctx.getTenantId(); // 也可按用户 "u:" + ctx.getUserId()

        long nowSec = System.currentTimeMillis() / 1000L;
        Window win = counters.computeIfAbsent(key, k -> new Window(nowSec, new AtomicInteger(0)));

        synchronized (win) {
            if (win.second != nowSec) {
                win.second = nowSec;
                win.counter.set(0);
            }
            int v = win.counter.incrementAndGet();
            if (v > props.getQps()) {
                return HandlerResult.stop(ApiResponse.error(ErrorCode.RATE_LIMITED, "Rate limit exceeded", traceId));
            }
        }
        return chain.proceed(ctx);
    }

    private static class Window {
        volatile long second;
        final AtomicInteger counter;
        Window(long second, AtomicInteger counter) {
            this.second = second; this.counter = counter;
        }
    }
}
// src/main/java/com/example/gateway/handler/GrayReleaseHandler.java
package com.example.gateway.handler;

import com.example.gateway.core.*;
import com.example.gateway.config.GrayProperties;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;

// 基于百分比与一致性哈希的灰度标记,向下游透传灰度标签
@Component("grayReleaseHandler")
public class GrayReleaseHandler implements Handler {
    private final GrayProperties props;

    public GrayReleaseHandler(GrayProperties props) {
        this.props = props;
    }

    @Override
    public String name() { return "GrayReleaseHandler"; }

    @Override
    public HandlerResult handle(RequestContext ctx, HandlerChain chain) throws Exception {
        int percent = Math.max(0, Math.min(100, props.getPercent()));
        if (percent <= 0) {
            ctx.setGrayTag("stable");
        } else {
            String base = ctx.getUserId() != null ? ctx.getUserId() : ctx.getTraceId();
            int bucket = consistentHashTo100(base);
            String tag = (bucket < percent) ? "gray" : "stable";
            ctx.setGrayTag(tag);
        }

        // 透传到下游(头/属性)
        HttpServletRequest req = ctx.getRequest();
        HttpServletResponse resp = ctx.getResponse();
        // 下游可从请求头/网关转发时使用该头
        resp.setHeader("X-Gray-Tag", ctx.getGrayTag());
        req.setAttribute("X-Gray-Tag", ctx.getGrayTag());

        return chain.proceed(ctx);
    }

    private int consistentHashTo100(String key) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(key.getBytes(StandardCharsets.UTF_8));
        int v = HexFormat.fromHexDigits(hash[0]);
        if (v < 0) v = -v;
        return v % 100;
    }
}
// src/main/java/com/example/gateway/handler/AuditHandler.java
package com.example.gateway.handler;

import com.example.gateway.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.concurrent.Executor;

// 审计异步处理:注册完成回调,不阻塞责任链
@Component("auditHandler")
public class AuditHandler implements Handler {
    private static final Logger log = LoggerFactory.getLogger(AuditHandler.class);
    private final Executor auditExecutor;

    public AuditHandler(Executor auditExecutor) {
        this.auditExecutor = auditExecutor;
    }

    @Override
    public String name() { return "AuditHandler"; }

    @Override
    public boolean isAsync() { return true; }

    @Override
    public HandlerResult handle(RequestContext ctx, HandlerChain chain) throws Exception {
        long begin = System.currentTimeMillis();
        // 在完成阶段异步审计:包括状态码、耗时、灰度标签、用户、租户、traceId等
        ctx.addCompletionCallback(c -> auditExecutor.execute(() -> {
            long cost = System.currentTimeMillis() - c.getStartTimeMs();
            Integer code = (Integer) c.getAttr("finalCode");
            String msg = (String) c.getAttr("finalMessage");
            log.info("AUDIT traceId={} tenant={} user={} gray={} code={} msg={} costMs={}",
                    c.getTraceId(), c.getTenantId(), c.getUserId(), c.getGrayTag(), code, msg, cost);
        }));
        return chain.proceed(ctx);
    }
}
  1. 网关过滤器与异常包装
// src/main/java/com/example/gateway/web/GatewayPreFilter.java
package com.example.gateway.web;

import com.example.gateway.common.ApiResponse;
import com.example.gateway.common.ErrorCode;
import com.example.gateway.common.GatewayException;
import com.example.gateway.config.ChainBuilder;
import com.example.gateway.core.DefaultHandlerChain;
import com.example.gateway.core.Handler;
import com.example.gateway.core.HandlerResult;
import com.example.gateway.core.RequestContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;

@Component
public class GatewayPreFilter extends OncePerRequestFilter {
    private final ChainBuilder chainBuilder;
    private final ObjectMapper objectMapper;

    public GatewayPreFilter(ChainBuilder chainBuilder, ObjectMapper objectMapper) {
        this.chainBuilder = chainBuilder;
        this.objectMapper = objectMapper;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String traceId = request.getHeader("X-Trace-Id");
        if (traceId == null || traceId.isBlank()) {
            traceId = UUID.randomUUID().toString();
        }
        MDC.put("traceId", traceId);
        response.setHeader("X-Trace-Id", traceId);

        RequestContext ctx = new RequestContext(request, response);
        ctx.setTraceId(traceId);

        List<Handler> handlers = chainBuilder.buildHandlers();
        DefaultHandlerChain chain = new DefaultHandlerChain(handlers);

        try {
            HandlerResult result = chain.proceed(ctx);
            if (result.isShortCircuited()) {
                ApiResponse<?> body = result.getErrorBody();
                ctx.putAttr("finalCode", body.getCode());
                ctx.putAttr("finalMessage", body.getMessage());
                writeJson(response, body);
                return;
            }

            // 通过前置链,继续后续过滤/控制器
            filterChain.doFilter(request, response);
            // 正常完成,标记最终结果(可通过响应状态或由业务控制器设置)
            ctx.putAttr("finalCode", 0);
            ctx.putAttr("finalMessage", "OK");
        } catch (GatewayException ge) {
            ApiResponse<?> body = ApiResponse.error(ge.getCode(), ge.getMessage(), traceId);
            ctx.putAttr("finalCode", body.getCode());
            ctx.putAttr("finalMessage", body.getMessage());
            writeJson(response, body);
        } catch (Exception e) {
            ApiResponse<?> body = ApiResponse.error(ErrorCode.INTERNAL_ERROR, "Unexpected error", traceId);
            ctx.putAttr("finalCode", body.getCode());
            ctx.putAttr("finalMessage", body.getMessage());
            writeJson(response, body);
        } finally {
            try { ctx.runCompletionCallbacks(); } finally { MDC.remove("traceId"); }
        }
    }

    private void writeJson(HttpServletResponse response, ApiResponse<?> body) throws IOException {
        byte[] bytes = objectMapper.writeValueAsBytes(body);
        response.setStatus(HttpServletResponse.SC_OK);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType("application/json");
        response.getOutputStream().write(bytes);
    }
}
  1. 示例控制器与应用入口
// src/main/java/com/example/gateway/web/DemoController.java
package com.example.gateway.web;

import com.example.gateway.common.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

// 模拟下游服务
@RestController
public class DemoController {
    @GetMapping("/api/echo")
    public ApiResponse<String> echo(HttpServletRequest req) {
        String traceId = req.getHeader("X-Trace-Id");
        String gray = (String) req.getAttribute("X-Gray-Tag");
        return ApiResponse.success("hello, gray=" + gray, traceId);
    }
}
// src/main/java/com/example/gateway/Application.java
package com.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
   public static void main(String[] args) {
       SpringApplication.run(Application.class, args);
   }
}
  1. 配置文件
# src/main/resources/application.yml
server:
  port: 8080

gateway:
  chain:
    steps:
      - bean: jwtAuthHandler
        enabled: true
        order: 10
      - bean: rateLimitHandler
        enabled: true
        order: 20
      - bean: grayReleaseHandler
        enabled: true
        order: 30
      - bean: auditHandler
        enabled: true
        order: 40

  ratelimit:
    qps: 2

  gray:
    percent: 30
  • 通过修改 gateway.chain.steps 中的 bean 顺序与 enabled 可动态调整链条与开关。
  • 若需运行时动态刷新,可将 GatewayChainProperties 标注 @RefreshScope 并配合配置中心。
  1. 单元测试(MockMvc)
// src/test/java/com/example/gateway/GatewayChainTest.java
package com.example.gateway;

import com.example.gateway.common.ErrorCode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class GatewayChainTest {
    @Autowired MockMvc mvc;
    @Autowired ObjectMapper om;

    @Test
    void ok_flow_should_pass_through_and_return_traceId() throws Exception {
        var res = mvc.perform(get("/api/echo")
                        .header("Authorization", "Bearer valid-token")
                        .header("X-Tenant-Id", "tenant-a"))
                .andExpect(status().isOk())
                .andExpect(header().exists("X-Trace-Id"))
                .andReturn();

        String json = res.getResponse().getContentAsString();
        JsonNode root = om.readTree(json);
        assertThat(root.get("code").asInt()).isEqualTo(0);
        assertThat(root.get("traceId").asText()).isEqualTo(res.getResponse().getHeader("X-Trace-Id"));
    }

    @Test
    void auth_missing_should_short_circuit() throws Exception {
        var res = mvc.perform(get("/api/echo")
                        .header("X-Tenant-Id", "tenant-a"))
                .andExpect(status().isOk())
                .andExpect(header().exists("X-Trace-Id"))
                .andReturn();

        JsonNode root = om.readTree(res.getResponse().getContentAsString());
        assertThat(root.get("code").asInt()).isEqualTo(ErrorCode.AUTH_FAILED.getCode());
        assertThat(root.get("message").asText()).contains("Authorization");
    }

    @Test
    void rate_limit_should_short_circuit() throws Exception {
        // QPS=2,快速发3次,其中第3次应触发限流
        mvc.perform(get("/api/echo")
                        .header("Authorization", "Bearer valid-token")
                        .header("X-Tenant-Id", "tenant-a"))
                .andExpect(status().isOk());

        mvc.perform(get("/api/echo")
                        .header("Authorization", "Bearer valid-token")
                        .header("X-Tenant-Id", "tenant-a"))
                .andExpect(status().isOk());

        var res3 = mvc.perform(get("/api/echo")
                        .header("Authorization", "Bearer valid-token")
                        .header("X-Tenant-Id", "tenant-a"))
                .andExpect(status().isOk())
                .andReturn();

        JsonNode root = om.readTree(res3.getResponse().getContentAsString());
        assertThat(root.get("code").asInt()).isEqualTo(ErrorCode.RATE_LIMITED.getCode());
    }
}
  1. 关键点回顾与扩展建议
  • 可插拔与动态顺序:通过 GatewayChainProperties + ChainBuilder 装配链表,按配置顺序执行,开启/关闭一步到位。
  • 短路机制:Handler 返回 HandlerResult.stop(ApiResponse) 即可终止链条,Filter 写出统一错误体。
  • 统一响应包装:错误统一由 ApiResponse 包装;成功可按需统一或交由业务控制器。
  • 异常捕获:Filter 捕获 GatewayException 和通用异常,避免错误外泄。
  • 同步/异步:核心校验同步执行;审计通过 completionCallbacks + 线程池异步处理,保证不阻塞主流程。
  • 链路追踪:traceId 贯穿 MDC、响应头与上下文,审计日志也携带 traceId;可扩展集成 Sleuth/OTel。
  • 扩展更多处理器:IP 白名单、风控、租户配额等,仅需实现 Handler 并在配置中加入 bean 名即可,无需改网关主流程。
  • 下游传递:示例通过响应头/请求属性透传灰度标签;在网关代理型场景中可加 ForwardFilter 将标签注入下游请求头。

至此,方案完整展示了责任链模式在 Spring Boot 微服务网关中的工程化落地,包括模式说明、应用分析以及可运行的 Java 代码示例。

示例详情

解决的问题

帮助开发者快速理解并实现指定的设计模式,通过详细说明与代码示例,减少开发时间,提高开发效率。

适用用户

软件开发工程师

利用提示词快速生成各类设计模式的代码实例,提高开发效率并深入理解设计原理,适用于日常开发、调试与学习。

编程入门者

通过生动的代码示例和清晰的原理解释,加快学习设计模式的进度,从基础知识转向实战应用。

技术架构师

快速构建复杂软件设计方案,高效填补架构开发中的空白,对接具体业务需求。

特征总结

快速生成常见设计模式的实现方案,将设计知识转化为实际应用。
自动生成完整的代码示例,节省开发时间和学习成本。
支持主流编程语言,自由选择满足多样化的技术需求。
提供设计模式的解决问题解释,帮助深度理解设计思路。
一键式使用,轻松切换不同系统类型定制专属解决方案。
设计模式知识与代码实例相结合,强化理论到实践的转化效率。
灵活定制代码输出风格和业务逻辑,匹配个性化需求。
优化开发效率,为复杂系统提供高效解决方案模板。

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥25.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 562 tokens
- 5 个可调节参数
{ 系统类型 } { 编程语言 } { 设计模式名称 } { 应用问题描述 } { 示例复杂度 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
限时免费

不要错过!

免费获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59