热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
本提示词可根据指定系统类型和编程语言,生成设计模式的实现方案,并附清晰代码示例,解释该模式如何解决实际问题,帮助开发者快速应用设计模式于系统架构和组件开发中。
观察者模式(Observer Pattern)通过建立“被观察者(Subject)—观察者(Observer)”的一对多依赖关系,使得当被观察者状态发生变化时,所有依赖它的观察者都会得到通知并自动更新。核心要点:
在电商后台 SPA 中,多个组件(订单列表、库存预警、客服通知、导航徽标等)需要消费来自 WebSocket 与轮询的混合事件。若每个组件各自维护连接与监听,将导致:
观察者模式的统一事件中心作为唯一 Subject,标准化事件输入(生产者 -> Subject)与输出(Subject -> 观察者),并通过主题与优先级控制分发,结合自动退订和缓存,解决上述问题。
目标与要点:
架构分层:
可靠性策略:
说明:
// ========== 工具函数 ==========
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();
关键点回顾:
将本示例替换为真实后端时:
以下方案基于责任链模式(Chain of Responsibility)为 Spring Boot API 网关构建可插拔、可配置、可扩展的请求处理链,满足“多步骤预处理、动态顺序与开关、短路返回统一错误体、异常捕获、同步与异步处理、链路追踪”等要求。
说明:
// 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));
}
}
// 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"); }
});
};
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
# 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
// 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());
}
}
至此,方案完整展示了责任链模式在 Spring Boot 微服务网关中的工程化落地,包括模式说明、应用分析以及可运行的 Java 代码示例。
帮助开发者快速理解并实现指定的设计模式,通过详细说明与代码示例,减少开发时间,提高开发效率。
利用提示词快速生成各类设计模式的代码实例,提高开发效率并深入理解设计原理,适用于日常开发、调试与学习。
通过生动的代码示例和清晰的原理解释,加快学习设计模式的进度,从基础知识转向实战应用。
快速构建复杂软件设计方案,高效填补架构开发中的空白,对接具体业务需求。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
免费获取高级提示词-优惠即将到期