¥
立即购买

类属性方法生成器

462 浏览
42 试用
12 购买
Nov 19, 2025更新

本提示词可根据用户输入生成完整类结构,包括属性、方法及可选构造函数与设计模式,实现面向对象高效建模,适用于前端组件化开发和快速类设计。

// TypeScript: FormFieldValidator // 用于前端表单单字段校验的可组合类,支持同步/异步规则、错误消息国际化、防抖与可观察订阅。 // 规则采用策略模式(每条规则为封装有 test 的策略对象),并提供预设工厂方法。

export type ValidateState = { valid: boolean; errors: string[] }; export type ObserverFn = (state: { valid: boolean; errors: string[]; value: any }) => void;

export type SyncRule = { name: string; test: (v: any) => boolean | string; // 返回 true 通过;false 或 string 失败(string 作为错误码或消息) };

export type AsyncRule = { name: string; test: (v: any) => Promise<boolean | string>; // 返回 true 通过;false 或 string 失败 };

export class FormFieldValidator { fieldName: string; value: any; rules: SyncRule[] = []; asyncRules: AsyncRule[] = []; errors: string[] = []; touched = false; debounceMs = 300; messages: Record<string, string> = {}; observers: Set = new Set();

private _debounceTimer: any = null; private _pendingAsyncId = 0;

constructor( fieldName: string, options?: { value?: any; debounceMs?: number; messages?: Record<string, string>; rules?: SyncRule[]; asyncRules?: AsyncRule[]; touched?: boolean; } ) { this.fieldName = fieldName; this.value = options?.value; if (typeof options?.debounceMs === 'number') this.debounceMs = options.debounceMs; if (options?.messages) this.messages = { ...options.messages }; if (options?.rules) this.rules = [...options.rules]; if (options?.asyncRules) this.asyncRules = [...options.asyncRules]; if (typeof options?.touched === 'boolean') this.touched = options.touched; }

// 执行同步规则(按序短路),更新 errors 并通知观察者 validateSync(): ValidateState { this.touched = true; const err = this.runSyncRules(); this.errors = err ? [err] : []; this.emit(); return { valid: this.errors.length === 0, errors: [...this.errors] }; }

// 执行异步规则(与同步结果合并),支持 trigger 控制防抖与短路 // - change: 应用防抖(默认 debounceMs) // - blur: 立即校验(无防抖) async validateAsync(trigger?: 'change' | 'blur'): Promise { this.touched = true;

// 先跑一次同步,若失败则短路整个异步流程
const syncRes = this.validateSync();
if (!syncRes.valid) {
  // 同步失败直接返回
  return { valid: false, errors: [...this.errors] };
}

// 同步通过再进行异步(按序短路)
const waitMs = trigger === 'blur' ? 0 : this.debounceMs;
const myId = ++this._pendingAsyncId;
if (this._debounceTimer) clearTimeout(this._debounceTimer);

return new Promise<ValidateState>((resolve) => {
  this._debounceTimer = setTimeout(async () => {
    // 若有新的请求发起,则取消本次执行结果
    if (myId !== this._pendingAsyncId) return;

    const err = await this.runAsyncRulesShortCircuit();
    if (myId !== this._pendingAsyncId) return;

    this.errors = err ? [err] : [];
    this.emit();
    resolve({ valid: this.errors.length === 0, errors: [...this.errors] });
  }, waitMs);
});

}

// 设置值,可选触发校验 setValue(val: any, options?: { validate?: boolean }): void { this.value = val; if (options?.validate) { // 常见在 change 上触发:先同步再异步(带防抖) this.validateSync(); // 异步校验不阻塞 this.validateAsync('change').catch(() => void 0); } else { // 不校验,仅通知值变更 this.emit(); } }

// 追加同步规则 addRule(rule: SyncRule): void { // 若同名存在,先移除保证唯一 this.rules = this.rules.filter((r) => r.name !== rule.name); this.rules.push(rule); }

// 追加异步规则 addAsyncRule(rule: AsyncRule): void { this.asyncRules = this.asyncRules.filter((r) => r.name !== rule.name); this.asyncRules.push(rule); }

// 按名称移除(同步与异步) removeRule(name: string): void { this.rules = this.rules.filter((r) => r.name !== name); this.asyncRules = this.asyncRules.filter((r) => r.name !== name); }

// 设置/覆盖错误文案(国际化) setMessages(map: Record<string, string>): void { this.messages = { ...this.messages, ...map }; }

// 重置状态 reset(): void { this.value = undefined; this.errors = []; this.touched = false; if (this._debounceTimer) { clearTimeout(this._debounceTimer); this._debounceTimer = null; } this._pendingAsyncId++; // 使任何在途异步结果失效 this.emit(); }

// 订阅状态变化 subscribe(fn: ObserverFn): () => void { this.observers.add(fn); return () => { this.observers.delete(fn); }; }

// 工厂方法:按预设快速创建 static fromPreset(preset: 'email' | 'mobile' | 'passwordStrong'): FormFieldValidator { switch (preset) { case 'email': { const v = new FormFieldValidator('email', { messages: { required: '{field}为必填项', 'email.invalid': '{field}格式不正确', }, }); // email 仅在非空时校验格式;是否必填由外部添加 required 规则控制 v.addRule({ name: 'email', test: (val: any) => { if (val === undefined || val === null || String(val).trim() === '') return true; const ok = /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(String(val)); return ok || 'email.invalid'; }, }); return v; } case 'mobile': { const v = new FormFieldValidator('mobile', { messages: { required: '{field}为必填项', 'mobile.invalid': '{field}格式不正确', }, }); v.addRule({ name: 'mobile', test: (val: any) => { if (val === undefined || val === null || String(val).trim() === '') return true; const ok = /^1[3-9]\d{9}$/.test(String(val)); return ok || 'mobile.invalid'; }, }); return v; } case 'passwordStrong': { const v = new FormFieldValidator('password', { messages: { required: '{field}为必填项', 'password.minLength': '{field}长度不能少于{min}个字符', 'password.weak': '{field}需要包含至少{kinds}种字符类型(大写、小写、数字、特殊符号)', }, }); // 长度规则,使用占位符:通过“错误码|参数”形式传参,如 "password.minLength|min=8" v.addRule({ name: 'password.minLength', test: (val: any) => { const s = String(val ?? ''); return s.length >= 8 || 'password.minLength|min=8'; }, }); // 至少包含两类字符:大写/小写/数字/特殊 v.addRule({ name: 'password.weak', test: (val: any) => { const s = String(val ?? ''); let kinds = 0; if (/[A-Z]/.test(s)) kinds++; if (/[a-z]/.test(s)) kinds++; if (/\d/.test(s)) kinds++; if (/[^A-Za-z0-9]/.test(s)) kinds++; return kinds >= 2 || 'password.weak|kinds=2'; }, }); return v; } } }

// ===== 内部实现 =====

private emit(): void { const state = { valid: this.errors.length === 0, errors: [...this.errors], value: this.value }; this.observers.forEach((fn) => { try { fn(state); } catch { // 忽略订阅者内部异常,避免影响外部流程 } }); }

private runSyncRules(): string | null { for (const rule of this.rules) { const res = rule.test(this.value); if (res === true) continue; const msg = this.resolveMessage(res, rule.name); return msg ?? rule.name; } return null; }

private async runAsyncRulesShortCircuit(): Promise<string | null> { for (const rule of this.asyncRules) { const res = await rule.test(this.value); if (res === true) continue; const msg = this.resolveMessage(res, rule.name); return msg ?? rule.name; } return null; }

// 将规则返回结果转换为最终的消息文本 // - true => null(通过) // - false => 使用规则名作为错误码 // - string => 作为“错误码或直出消息”。如果包含“|k=v”参数,视为错误码+占位符参数 private resolveMessage(result: boolean | string, ruleName: string): string | null { if (result === true) return null;

// 将 false 转换为规则名(作为错误码)
const token = result === false ? ruleName : String(result);

const parsed = this.parseErrorToken(token);
const template = this.messages[parsed.code];

if (template) {
  return this.formatWithParams(template, parsed.params);
}

// 若找不到模板,则将 code 当作消息文本直出(忽略参数部分)
return parsed.code;

}

// 解析“错误码|k1=v1&k2=v2”形式的 token private parseErrorToken(token: string): { code: string; params: Record<string, string> } { const [code, rawParams] = token.split('|', 2); const params: Record<string, string> = {}; if (rawParams) { // 支持 &、, 或 ; 分隔 rawParams.split(/[&,;]+/).forEach((pair) => { const [k, v] = pair.split('='); if (k) params[k.trim()] = decodeURIComponent((v ?? '').trim()); }); } return { code, params }; }

// 用占位符替换:支持 {field} / {value} 以及自定义参数 private formatWithParams(template: string, params?: Record<string, any>): string { const merged = { field: this.fieldName, value: this.value, ...(params || {}), }; return template.replace(/{(\w+)}/g, (_, k) => { const v = merged[k]; return v === null || v === undefined ? '' : String(v); }); } }

// 示例:常用规则可按如下方式外部添加 // const v = new FormFieldValidator('username', { messages: { required: '{field}为必填项' } }); // v.addRule({ name: 'required', test: v => (v != null && String(v).trim() !== '') || 'required' }); // v.addAsyncRule({ name: 'unique', test: async v => (await apiCheckUnique(v)) || 'username.taken' }); // v.setMessages({ 'username.taken': '{field}已被占用' });

// SPDX-License-Identifier: MIT package com.example.ecommerce.cart;

import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.time.Clock; import java.time.Instant; import java.util.*;

/**

  • 购物车聚合根:管理购物项、优惠与结算,保证金额精度与货币一致性。

    • 同一货币约束
    • 策略模式:可插拔折扣与运费策略
    • 构建者:可扩展促销规则与外部服务 */ public final class ShoppingCart {

    // 基础配置 private final Currency currency; // 购物车货币(单一) private final MathContext mathContext; // 金额计算精度 private final RoundingMode roundingMode; // 金额舍入规则 private final int moneyScale; // 金额小数位(通常 2) private final Clock clock; // 可注入时钟,便于测试

    // 聚合内数据 private final List items; // 购物项集合 private final List discountStrategies; // 折扣策略链 private ShippingPolicy shippingPolicy; // 运费策略 private final Set couponCodes; // 已应用优惠券(去重) private BigDecimal subtotalCache; // 小计缓存(派生) private boolean subtotalDirty; // 缓存失效标记

    private Instant updatedAt; // 最近更新时间 private String idempotencyKey; // 幂等键(结算/下单防重)

    // 外部协作者(策略/服务) private final InventoryPolicy inventoryPolicy; // 库存/最小购买校验策略 private final CouponValidator couponValidator; // 优惠券校验器 private final PaymentPreparation paymentPreparation; // 支付前准备(可与支付网关协作)

    // ======================= 构造与构建者 =======================

    private ShoppingCart(Builder b) { Objects.requireNonNull(b.currency, "currency required"); this.currency = b.currency; this.mathContext = b.mathContext != null ? b.mathContext : MathContext.DECIMAL64; this.roundingMode = b.roundingMode != null ? b.roundingMode : RoundingMode.HALF_UP; this.moneyScale = b.moneyScale > 0 ? b.moneyScale : 2; this.clock = b.clock != null ? b.clock : Clock.systemUTC();

     this.items = new ArrayList<>();
     if (b.initialItems != null) {
         for (CartItem it : b.initialItems) {
             // 防止不同货币:单价不含币种,视为与购物车币种一致
             addItem(it.getSkuId(), it.getTitle(), it.getUnitPrice(), it.getQty(), it.getAttrs());
         }
     }
    
     this.discountStrategies = new ArrayList<>(b.discountStrategies != null ? b.discountStrategies : Collections.emptyList());
     this.shippingPolicy = Objects.requireNonNullElseGet(b.shippingPolicy, () -> new FreeShippingThresholdPolicy(Money.zero(currency), Money.zero(currency)));
     this.couponCodes = new LinkedHashSet<>(b.initialCoupons != null ? b.initialCoupons : Collections.emptySet());
     this.inventoryPolicy = Objects.requireNonNullElseGet(b.inventoryPolicy, NoopInventoryPolicy::new);
     this.couponValidator = Objects.requireNonNullElseGet(b.couponValidator, () -> CouponValidationResult.invalid("no validator"));
     this.paymentPreparation = Objects.requireNonNullElseGet(b.paymentPreparation, NoopPaymentPreparation::new);
    
     this.subtotalCache = BigDecimal.ZERO.setScale(moneyScale, roundingMode);
     this.subtotalDirty = true;
     this.updatedAt = clock.instant();
     this.idempotencyKey = b.idempotencyKey;
    

    }

    public static Builder builder() { return new Builder(); }

    // ======================= 聚合根操作 =======================

    /**

    • 若已存在同款(skuId + attrs 相同)则合并数量;价格以最新为准。 */ public synchronized void addItem(String skuId, String title, BigDecimal unitPrice, int qty, Map<String, String> attrs) { requireNonBlank(skuId, "skuId"); requireNonBlank(title, "title"); Objects.requireNonNull(unitPrice, "unitPrice"); if (qty <= 0) throw new IllegalArgumentException("qty must be > 0"); Map<String, String> safeAttrs = attrs == null ? Collections.emptyMap() : new TreeMap<>(attrs);

      BigDecimal normalizedPrice = normalizeAmount(unitPrice); Optional existing = findItem(skuId, safeAttrs);

      int newQty = qty; if (existing.isPresent()) { CartItem item = existing.get(); newQty = Math.addExact(item.getQty(), qty); // 库存与最小购买校验 validateQtyRules(skuId, safeAttrs, newQty); // 合并:更新数量与价格 item.setQty(newQty); item.setUnitPrice(normalizedPrice); } else { // 校验 validateQtyRules(skuId, safeAttrs, qty); items.add(new CartItem(skuId, title, normalizedPrice, qty, safeAttrs)); }

      markDirty(); }

    /**

    • 按 SKU 移除购物项(移除该 SKU 的所有变体)。 */ public synchronized void removeItem(String skuId) { requireNonBlank(skuId, "skuId"); boolean removed = items.removeIf(i -> i.getSkuId().equals(skuId)); if (removed) { markDirty(); } }

    /**

    • 调整指定 SKU 的数量(如果存在多个变体,全部调整为该数量;如需变体级别,可扩展重载)。
    • qty <= 0 则移除该 SKU。 */ public synchronized void changeQty(String skuId, int qty) { requireNonBlank(skuId, "skuId"); if (qty <= 0) { removeItem(skuId); return; } boolean any = false; for (CartItem it : items) { if (it.getSkuId().equals(skuId)) { validateQtyRules(it.getSkuId(), it.getAttrs(), qty); it.setQty(qty); any = true; } } if (!any) { throw new NoSuchElementException("SKU not in cart: " + skuId); } markDirty(); }

    /**

    • 应用优惠券:验证有效期、可叠加性与适用范围后记录。
    • 返回是否成功应用(重复添加返回 false)。 */ public synchronized boolean applyCoupon(String code) { requireNonBlank(code, "coupon code"); if (couponCodes.contains(code)) { return false; // 去重 } CouponValidationResult result = couponValidator.validate(code, this, Collections.unmodifiableSet(couponCodes)); if (!result.isValid()) { return false; } // 可叠加性由策略链在计算折扣时综合判断;此处仅记录 couponCodes.add(code); touch(); return true; }

    /**

    • 计算应付总额:subtotal + shipping(估算) - discount。
    • 注意:如需地址敏感的运费,请使用 calculateTotal(Address)。 */ public synchronized Money calculateTotal() { return calculateTotal(null); }

    /**

    • 计算应付总额(地址可选)。 */ public synchronized Money calculateTotal(Address shippingAddress) { Money subtotal = getSubtotalMoney(); Money shipping = shippingPolicy.estimate(this, Optional.ofNullable(shippingAddress)); Money discount = calculateDiscounts(subtotal, shipping, shippingAddress); Money total = subtotal.add(shipping).subtract(discount); if (total.isNegative()) { total = Money.zero(currency); } return total; }

    /**

    • 结算:生成订单草案、锁定库存并返回应付信息。

      • 幂等键透传至支付网关/准备阶段
      • 返回应付金额、运费、折扣、已用优惠券与库存锁定凭证等 */ public synchronized CheckoutResult checkout(PaymentGateway gateway, Address shippingAddress) { Objects.requireNonNull(gateway, "PaymentGateway required"); Objects.requireNonNull(shippingAddress, "shipping address required");

      if (items.isEmpty()) { throw new IllegalStateException("cart is empty"); }

      // 1) 计算金额 Money subtotal = getSubtotalMoney(); Money shipping = shippingPolicy.estimate(this, Optional.of(shippingAddress)); Money discount = calculateDiscounts(subtotal, shipping, shippingAddress); Money totalPayable = subtotal.add(shipping).subtract(discount); if (totalPayable.isNegative()) totalPayable = Money.zero(currency);

      // 2) 锁定库存 String reservationToken = inventoryPolicy.reserve(items);

      // 3) 准备支付(可用于创建支付意图等) String idem = getOrGenerateIdempotencyKey(); PaymentPreparation.PreparedPayment prepared = paymentPreparation.prepare(totalPayable, idem, shippingAddress); PaymentGateway.PaymentIntent intent = gateway.createPaymentIntent(totalPayable, idem, prepared);

      // 4) 生成 CheckoutResult CheckoutResult result = new CheckoutResult( UUID.randomUUID().toString(), // orderDraftId subtotal, shipping, discount, totalPayable, Collections.unmodifiableList(new ArrayList<>(items)), Collections.unmodifiableSet(new LinkedHashSet<>(couponCodes)), reservationToken, intent.intentId(), intent.raw() );

      touch(); // 更新更新时间 return result; }

    /**

    • 清空购物车与派生缓存。 */ public synchronized void clear() { items.clear(); couponCodes.clear(); subtotalCache = Money.zero(currency).amount(); subtotalDirty = true; touch(); }

    public static Builder builder(Currency currency) { return new Builder().currency(currency); }

    // ======================= 内部计算/校验 =======================

    private Optional findItem(String skuId, Map<String, String> attrs) { for (CartItem it : items) { if (it.getSkuId().equals(skuId) && it.getAttrs().equals(attrs)) { return Optional.of(it); } } return Optional.empty(); }

    private void validateQtyRules(String skuId, Map<String, String> attrs, int qty) { if (qty < 1) throw new IllegalArgumentException("min qty is 1"); int max = inventoryPolicy.getMaxPurchasable(skuId, attrs); if (qty > max) { throw new IllegalArgumentException("qty exceeds available limit: " + max); } }

    private Money calculateDiscounts(Money subtotal, Money shipping, Address shippingAddress) { if (discountStrategies.isEmpty()) return Money.zero(currency);

     DiscountContext ctx = new DiscountContext(
             currency, mathContext, roundingMode, moneyScale,
             Collections.unmodifiableSet(couponCodes),
             subtotal, shipping, Optional.ofNullable(shippingAddress));
    
     Money totalDiscount = Money.zero(currency);
     for (DiscountStrategy strategy : discountStrategies) {
         Discount d = strategy.apply(this, ctx);
         if (d == null || d.amount() == null) continue;
         Money delta = d.amount();
         if (!delta.currency().equals(currency)) {
             throw new IllegalStateException("discount currency mismatch");
         }
         if (delta.isNegative()) continue; // 保护,不支持负折扣
         totalDiscount = totalDiscount.add(delta);
     }
     // 折扣不得超过 subtotal + shipping
     Money cap = subtotal.add(shipping);
     if (totalDiscount.gt(cap)) totalDiscount = cap;
     return totalDiscount;
    

    }

    private Money getSubtotalMoney() { BigDecimal amt = getSubtotalAmount(); return Money.of(amt, currency, moneyScale, roundingMode); }

    private BigDecimal getSubtotalAmount() { if (subtotalDirty) { BigDecimal sum = BigDecimal.ZERO.setScale(moneyScale, roundingMode); for (CartItem it : items) { BigDecimal line = it.getUnitPrice().multiply(BigDecimal.valueOf(it.getQty()), mathContext); line = line.setScale(moneyScale, roundingMode); sum = sum.add(line, mathContext).setScale(moneyScale, roundingMode); } subtotalCache = sum; subtotalDirty = false; } return subtotalCache; }

    private void markDirty() { subtotalDirty = true; touch(); }

    private void touch() { updatedAt = clock.instant(); }

    private BigDecimal normalizeAmount(BigDecimal amount) { return amount.setScale(moneyScale, roundingMode); }

    private static void requireNonBlank(String s, String name) { if (s == null || s.trim().isEmpty()) throw new IllegalArgumentException(name + " required"); }

    private String getOrGenerateIdempotencyKey() { if (idempotencyKey == null || idempotencyKey.isBlank()) { idempotencyKey = "cart-" + UUID.randomUUID(); } return idempotencyKey; }

    // ======================= 对外可读属性(只读视图) =======================

    public Currency getCurrency() { return currency; } public List getItems() { return Collections.unmodifiableList(items); } public List getDiscountStrategies() { return Collections.unmodifiableList(discountStrategies); } public ShippingPolicy getShippingPolicy() { return shippingPolicy; } public Set getCouponCodes() { return Collections.unmodifiableSet(couponCodes); } public BigDecimal getSubtotal() { return getSubtotalAmount(); } public Instant getUpdatedAt() { return updatedAt; } public String getIdempotencyKey() { return idempotencyKey; }

    // 可选:动态更换运费策略(例如结算时基于地址) public synchronized void setShippingPolicy(ShippingPolicy shippingPolicy) { this.shippingPolicy = Objects.requireNonNull(shippingPolicy); touch(); }

    public synchronized void setIdempotencyKey(String idempotencyKey) { this.idempotencyKey = idempotencyKey; touch(); }

    // ======================= 嵌套类型:值对象/策略/接口 =======================

    public static final class CartItem { private final String skuId; private final String title; private BigDecimal unitPrice; // 与购物车货币一致 private int qty; private final Map<String, String> attrs;

     CartItem(String skuId, String title, BigDecimal unitPrice, int qty, Map<String, String> attrs) {
         this.skuId = skuId;
         this.title = title;
         this.unitPrice = unitPrice;
         this.qty = qty;
         this.attrs = Collections.unmodifiableMap(new TreeMap<>(attrs));
     }
    
     public String getSkuId() { return skuId; }
     public String getTitle() { return title; }
     public BigDecimal getUnitPrice() { return unitPrice; }
     public int getQty() { return qty; }
     public Map<String, String> getAttrs() { return attrs; }
    
     void setUnitPrice(BigDecimal unitPrice) {
         this.unitPrice = unitPrice;
     }
     void setQty(int qty) {
         this.qty = qty;
     }
    
     @Override
     public String toString() {
         return "CartItem{" +
                 "skuId='" + skuId + '\'' +
                 ", title='" + title + '\'' +
                 ", unitPrice=" + unitPrice +
                 ", qty=" + qty +
                 ", attrs=" + attrs +
                 '}';
     }
    

    }

    public record Money(BigDecimal amount, Currency currency) { public static Money of(BigDecimal amount, Currency currency, int scale, RoundingMode rm) { return new Money(amount.setScale(scale, rm), currency); } public static Money zero(Currency c) { return new Money(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), c); } public Money add(Money other) { requireSameCurrency(other); return new Money(amount.add(other.amount), currency); } public Money subtract(Money other) { requireSameCurrency(other); return new Money(amount.subtract(other.amount), currency); } public boolean isNegative() { return amount.signum() < 0; } public boolean gt(Money other) { requireSameCurrency(other); return amount.compareTo(other.amount) > 0; } private void requireSameCurrency(Money other) { if (!currency.equals(other.currency)) throw new IllegalArgumentException("currency mismatch"); } public BigDecimal amount() { return amount; } // alias }

    public interface DiscountStrategy { Discount apply(ShoppingCart cart, DiscountContext ctx); }

    public static final class DiscountContext { public final Currency currency; public final MathContext mathContext; public final RoundingMode roundingMode; public final int moneyScale; public final Set couponCodes; public final Money subtotal; public final Money shipping; public final Optional

    shippingAddress;

     public DiscountContext(Currency currency,
                            MathContext mathContext,
                            RoundingMode roundingMode,
                            int moneyScale,
                            Set<String> couponCodes,
                            Money subtotal,
                            Money shipping,
                            Optional<Address> shippingAddress) {
         this.currency = currency;
         this.mathContext = mathContext;
         this.roundingMode = roundingMode;
         this.moneyScale = moneyScale;
         this.couponCodes = couponCodes;
         this.subtotal = subtotal;
         this.shipping = shipping;
         this.shippingAddress = shippingAddress;
     }
    
     public Money money(BigDecimal amt) {
         return Money.of(amt, currency, moneyScale, roundingMode);
     }
    

    }

    public record Discount(Money amount, String reason, Map<String, Object> meta) { public static Discount of(Money amount, String reason) { return new Discount(amount, reason, Collections.emptyMap()); } }

    public interface ShippingPolicy { Money estimate(ShoppingCart cart, Optional

    shippingAddress); }

    /**

    • 示例运费策略:满额包邮,否则固定运费;可根据区域差异化(演示结构)。 */ public static final class FreeShippingThresholdPolicy implements ShippingPolicy { private final Money threshold; private final Money baseFee;

      public FreeShippingThresholdPolicy(Money threshold, Money baseFee) { this.threshold = threshold; this.baseFee = baseFee; }

      @Override public Money estimate(ShoppingCart cart, Optional

      shippingAddress) { Money subtotal = cart.getSubtotalMoney(); if (!subtotal.currency().equals(threshold.currency)) { throw new IllegalStateException("currency mismatch in shipping policy"); } // 区域差异示意:海外加收 50% boolean overseas = shippingAddress.map(a -> !"CN".equalsIgnoreCase(a.country())).orElse(false); if (subtotal.amount().compareTo(threshold.amount) >= 0) { return Money.of(BigDecimal.ZERO, threshold.currency, 2, RoundingMode.HALF_UP); } Money fee = baseFee; if (overseas) { BigDecimal up = fee.amount().multiply(BigDecimal.valueOf(1.5)); fee = Money.of(up, fee.currency(), 2, RoundingMode.HALF_UP); } return fee; } }

    public interface CouponValidator { CouponValidationResult validate(String code, ShoppingCart cart, Set existingCodes); }

    public interface CouponValidationResult extends CouponValidator { boolean isValid(); String reason();

     static CouponValidationResult ok() {
         return new CouponValidationResult() {
             public boolean isValid() { return true; }
             public String reason() { return "OK"; }
             @Override public CouponValidationResult validate(String code, ShoppingCart cart, Set<String> existingCodes) { return this; }
         };
     }
    
     static CouponValidationResult invalid(String reason) {
         return new CouponValidationResult() {
             public boolean isValid() { return false; }
             public String reason() { return reason; }
             @Override public CouponValidationResult validate(String code, ShoppingCart cart, Set<String> existingCodes) { return this; }
         };
     }
    

    }

    public interface InventoryPolicy { int getMaxPurchasable(String skuId, Map<String, String> attrs); String reserve(List items); }

    public static final class NoopInventoryPolicy implements InventoryPolicy { @Override public int getMaxPurchasable(String skuId, Map<String, String> attrs) { return Integer.MAX_VALUE / 4; } @Override public String reserve(List items) { return "noop-reservation-" + UUID.randomUUID(); } }

    public interface PaymentPreparation { PreparedPayment prepare(Money total, String idempotencyKey, Address shippingAddress); record PreparedPayment(Map<String, Object> meta) {} }

    public static final class NoopPaymentPreparation implements PaymentPreparation { @Override public PreparedPayment prepare(Money total, String idempotencyKey, Address shippingAddress) { return new PreparedPayment(Map.of("prepared", true)); } }

    public interface PaymentGateway { PaymentIntent createPaymentIntent(Money amount, String idempotencyKey, PaymentPreparation.PreparedPayment prepared); record PaymentIntent(String intentId, Map<String, Object> raw) {} }

    public record Address(String country, String region, String city, String postcode, String addressLine) {}

    public static final class CheckoutResult { private final String orderDraftId; private final Money subtotal; private final Money shipping; private final Money discount; private final Money totalPayable; private final List lineItems; private final Set appliedCoupons; private final String inventoryReservationToken; private final String paymentIntentId; private final Map<String, Object> paymentRaw;

     public CheckoutResult(String orderDraftId,
                           Money subtotal,
                           Money shipping,
                           Money discount,
                           Money totalPayable,
                           List<CartItem> lineItems,
                           Set<String> appliedCoupons,
                           String inventoryReservationToken,
                           String paymentIntentId,
                           Map<String, Object> paymentRaw) {
         this.orderDraftId = orderDraftId;
         this.subtotal = subtotal;
         this.shipping = shipping;
         this.discount = discount;
         this.totalPayable = totalPayable;
         this.lineItems = lineItems;
         this.appliedCoupons = appliedCoupons;
         this.inventoryReservationToken = inventoryReservationToken;
         this.paymentIntentId = paymentIntentId;
         this.paymentRaw = paymentRaw;
     }
    
     public String orderDraftId() { return orderDraftId; }
     public Money subtotal() { return subtotal; }
     public Money shipping() { return shipping; }
     public Money discount() { return discount; }
     public Money totalPayable() { return totalPayable; }
     public List<CartItem> lineItems() { return lineItems; }
     public Set<String> appliedCoupons() { return appliedCoupons; }
     public String inventoryReservationToken() { return inventoryReservationToken; }
     public String paymentIntentId() { return paymentIntentId; }
     public Map<String, Object> paymentRaw() { return paymentRaw; }
    

    }

    // ======================= 构建者 =======================

    public static final class Builder { private Currency currency; private List initialItems; private List discountStrategies; private ShippingPolicy shippingPolicy; private Set initialCoupons; private InventoryPolicy inventoryPolicy; private CouponValidator couponValidator; private PaymentPreparation paymentPreparation;

     private MathContext mathContext;
     private RoundingMode roundingMode;
     private int moneyScale = 2;
     private Clock clock;
     private String idempotencyKey;
    
     public Builder currency(Currency currency) { this.currency = currency; return this; }
     public Builder items(List<CartItem> items) { this.initialItems = items; return this; }
     public Builder discountStrategies(List<DiscountStrategy> strategies) { this.discountStrategies = strategies; return this; }
     public Builder addDiscountStrategy(DiscountStrategy s) {
         if (this.discountStrategies == null) this.discountStrategies = new ArrayList<>();
         this.discountStrategies.add(s);
         return this;
     }
     public Builder shippingPolicy(ShippingPolicy policy) { this.shippingPolicy = policy; return this; }
     public Builder coupons(Set<String> coupons) { this.initialCoupons = coupons; return this; }
     public Builder inventoryPolicy(InventoryPolicy policy) { this.inventoryPolicy = policy; return this; }
     public Builder couponValidator(CouponValidator validator) { this.couponValidator = validator; return this; }
     public Builder paymentPreparation(PaymentPreparation prep) { this.paymentPreparation = prep; return this; }
    
     public Builder mathContext(MathContext mc) { this.mathContext = mc; return this; }
     public Builder roundingMode(RoundingMode rm) { this.roundingMode = rm; return this; }
     public Builder moneyScale(int scale) { this.moneyScale = scale; return this; }
     public Builder clock(Clock clock) { this.clock = clock; return this; }
     public Builder idempotencyKey(String key) { this.idempotencyKey = key; return this; }
    
     public ShoppingCart build() {
         return new ShoppingCart(this);
     }
    

    }

    // ======================= 示例折扣策略(可选) =======================

    /**

    • 示例:满减策略。满 X 减 Y,不可为负。 */ public static final class FullReductionStrategy implements DiscountStrategy { private final Money threshold; private final Money reduction;

      public FullReductionStrategy(Money threshold, Money reduction) { this.threshold = threshold; this.reduction = reduction; if (!threshold.currency.equals(reduction.currency)) { throw new IllegalArgumentException("currency mismatch in FullReductionStrategy"); } }

      @Override public Discount apply(ShoppingCart cart, DiscountContext ctx) { if (!ctx.subtotal.currency().equals(threshold.currency)) return null; if (ctx.subtotal.amount().compareTo(threshold.amount) >= 0) { return Discount.of(Money.of(reduction.amount, reduction.currency, ctx.moneyScale, ctx.roundingMode), "满减"); } return null; } }

    /**

    • 示例:会员折扣(百分比)。 */ public static final class MemberPercentageOffStrategy implements DiscountStrategy { private final BigDecimal percent; // 0.05 表示 5%

      public MemberPercentageOffStrategy(BigDecimal percent) { if (percent == null || percent.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("percent must be > 0"); } this.percent = percent; }

      @Override public Discount apply(ShoppingCart cart, DiscountContext ctx) { BigDecimal amt = ctx.subtotal.amount().multiply(percent, ctx.mathContext); amt = amt.setScale(ctx.moneyScale, ctx.roundingMode); if (amt.signum() <= 0) return null; return Discount.of(ctx.money(amt), "会员折扣"); } }

    /**

    • 示例:优惠券策略(基于已应用 couponCodes,简单演示)。 */ public static final class CouponDiscountStrategy implements DiscountStrategy { @Override public Discount apply(ShoppingCart cart, DiscountContext ctx) { if (ctx.couponCodes.isEmpty()) return null; // 简单规则演示:SAVE10 -> 减 10;SAVE20 -> 满 100 减 20;不可为负 BigDecimal total = BigDecimal.ZERO; boolean save10 = ctx.couponCodes.contains("SAVE10"); boolean save20 = ctx.couponCodes.contains("SAVE20"); if (save10) total = total.add(BigDecimal.TEN); if (save20 && ctx.subtotal.amount().compareTo(new BigDecimal("100.00")) >= 0) { total = total.add(new BigDecimal("20.00")); } if (total.signum() <= 0) return null; return Discount.of(ctx.money(total), "优惠券"); } } }

下面给出一个可直接复制使用的 C# 实现,包含 EventBus 类及其所需的配套接口/选项类型。该实现特点:

  • 主题隔离:每个 topic 单独维护订阅者与粘性事件。
  • 同步/异步分发:Publish 可选择同步分发(立即执行)或异步分发(通过 Channel 队列背压)。
  • 中间件链:按注册顺序流水线处理事件(支持鉴权、过滤、埋点等),返回 null 可拦截事件。
  • 订阅控制:一次性订阅(Once)、优先级(Priority)、并发度(MaxConcurrency)、新订阅者是否立即重放粘性事件(ReplayStickyImmediately)。
  • 粘性事件与重放:支持粘性事件存储与 Replay。
  • 事务性发布:PublishOptions 支持传入 EventBusTransaction,将事件缓冲直至 Commit。
  • Flush/Dispose:Flush 等待队列与在途事件清空;Dispose 优雅关闭。

说明:

  • 为方便集成,使用 Microsoft.Extensions.Logging 的 ILogger(可选依赖)。
  • subscribers 的 value 为 List,通过锁保证该 List 的并发安全;字典本身为 ConcurrentDictionary。
  • IEvent 统一为 Envelope,携带 Topic、Payload、类型、时间戳、是否粘性等元信息。

代码如下:

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.Logging;

namespace LightweightEventBus { // 事件包裹统一接口 public interface IEvent { string Topic { get; } object Payload { get; } Type PayloadType { get; } DateTimeOffset Timestamp { get; } bool Sticky { get; } // 可扩展元数据 IReadOnlyDictionary<string, object?> Metadata { get; } }

// 订阅处理器接口(供 subscribers 字典保存)
public interface IHandler
{
    int Priority { get; }
    int MaxConcurrency { get; }
    bool Once { get; }
    Type EventType { get; }
    Delegate OriginalDelegate { get; } // 用于 Unsubscribe 匹配
    Task Invoke(IEvent evt, CancellationToken ct);
    SemaphoreSlim Semaphore { get; } // 控制并发度
}

// 订阅参数
public sealed class SubscribeOptions
{
    // 一次性订阅(处理一次后自动取消)
    public bool Once { get; set; } = false;
    // 优先级(数值越大越优先)
    public int Priority { get; set; } = 0;
    // 每个处理器的最大并发度(>1 时允许同一处理器并发处理多个事件)
    public int MaxConcurrency { get; set; } = 1;
    // 订阅建立后是否立即重放粘性事件(若存在)
    public bool ReplayStickyImmediately { get; set; } = true;
}

// 发布参数
public sealed class PublishOptions
{
    // 延迟发布(同步/异步均可生效)
    public TimeSpan? Delay { get; set; }
    // 是否作为粘性事件存储
    public bool Sticky { get; set; } = false;
    // 是否同步分发(true 则在当前调用栈中经过中间件并直接分发到订阅者)
    public bool Synchronous { get; set; } = false;
    // 事务:若提供则事件先缓冲,待 Commit 后统一入队分发
    public EventBus.EventBusTransaction? Transaction { get; set; }
    // 附加元数据(可用于追踪/链路)
    public Dictionary<string, object?>? Metadata { get; set; }
}

// EventBus 实现
public sealed class EventBus : IDisposable
{
    // 必要属性(按需求命名)
    public ConcurrentDictionary<string, List<IHandler>> subscribers { get; }
    public Channel<IEvent> queue { get; }
    public bool enableReplay { get; }
    public Dictionary<string, IEvent> stickyEvents { get; }
    public List<Func<IEvent, Task<IEvent?>>> middlewares { get; }
    public ILogger? logger { get; }

    // 内部状态
    private readonly CancellationTokenSource _cts = new();
    private readonly Task _consumerTask;
    private readonly object _stickyLock = new();
    private volatile bool _disposed;

    // Flush 支持
    private long _enqueued;    // 入队总数(或同步路径计作入处理)
    private long _processed;   // 完成处理的总数
    private TaskCompletionSource<bool> _idleTcs = CreateIdleTcs(completed: true);

    // 构造函数
    public EventBus(
        int capacity = 1024,
        BoundedChannelFullMode fullMode = BoundedChannelFullMode.Wait,
        bool enableReplay = true,
        ILogger? logger = null)
    {
        this.logger = logger;
        this.enableReplay = enableReplay;

        subscribers = new ConcurrentDictionary<string, List<IHandler>>(StringComparer.Ordinal);
        stickyEvents = new Dictionary<string, IEvent>(StringComparer.Ordinal);
        middlewares = new List<Func<IEvent, Task<IEvent?>>>();

        var channel = Channel.CreateBounded<IEvent>(new BoundedChannelOptions(capacity)
        {
            SingleReader = true,
            SingleWriter = false,
            FullMode = fullMode
        });
        queue = channel;

        _consumerTask = Task.Run(() => ConsumeLoop(channel.Reader, _cts.Token));
    }

    // 订阅
    public IDisposable Subscribe<TEvent>(string topic, Func<TEvent, Task> handler, SubscribeOptions? options = null)
    {
        if (string.IsNullOrWhiteSpace(topic))
            throw new ArgumentException("topic is required", nameof(topic));
        if (handler is null)
            throw new ArgumentNullException(nameof(handler));
        ThrowIfDisposed();

        options ??= new SubscribeOptions();

        var list = subscribers.GetOrAdd(topic, _ => new List<IHandler>());
        var h = new Handler<TEvent>(handler, options);

        lock (list)
        {
            list.Add(h);
            // 按优先级降序排列,保证分发顺序
            list.Sort(static (a, b) => b.Priority.CompareTo(a.Priority));
        }

        // 新订阅者立即获取粘性事件
        if (enableReplay && options.ReplayStickyImmediately)
        {
            IEvent? sticky = null;
            lock (_stickyLock)
            {
                if (stickyEvents.TryGetValue(topic, out var evt))
                {
                    sticky = evt;
                }
            }

            if (sticky is not null)
            {
                // 单独投递给该 handler(不影响其他订阅者)
                _ = DispatchToSingleHandlerAsync(topic, sticky, h, _cts.Token);
            }
        }

        // 返回可释放对象用于取消订阅
        return new Unsubscriber(() => Unsubscribe(topic, h));
    }

    // 取消订阅(通过原始委托匹配)
    public void Unsubscribe<TEvent>(string topic, Func<TEvent, Task> handler)
    {
        if (string.IsNullOrWhiteSpace(topic))
            return;

        if (!subscribers.TryGetValue(topic, out var list))
            return;

        lock (list)
        {
            var toRemove = list
                .OfType<Handler<TEvent>>()
                .FirstOrDefault(h => ReferenceEquals(h.OriginalDelegate, handler));
            if (toRemove != null)
            {
                list.Remove(toRemove);
                toRemove.Dispose();
            }
        }
    }

    // 内部取消订阅重载(用于一次性订阅完成后自动移除)
    private void Unsubscribe(string topic, IHandler handler)
    {
        if (!subscribers.TryGetValue(topic, out var list))
            return;

        lock (list)
        {
            if (list.Remove(handler))
            {
                (handler as IDisposable)?.Dispose();
            }
        }
    }

    // 发布
    public async Task Publish<TEvent>(string topic, TEvent evt, PublishOptions? options = null, CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(topic))
            throw new ArgumentException("topic is required", nameof(topic));
        ThrowIfDisposed();

        options ??= new PublishOptions();

        if (options.Delay is { } delay && delay > TimeSpan.Zero)
        {
            await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
        }

        var envelope = CreateEnvelope(topic, evt!, options);

        // 事务性:若携带事务,则缓冲,等待 Commit
        if (options.Transaction is not null)
        {
            options.Transaction.Enqueue(envelope);
            return;
        }

        // 粘性事件存储(立即保存最后一次)
        if (enableReplay && options.Sticky)
        {
            lock (_stickyLock)
            {
                stickyEvents[topic] = envelope;
            }
        }

        if (options.Synchronous)
        {
            // 同步:直接执行中间件和分发
            IncrementEnqueued();
            try
            {
                var processed = await RunMiddlewares(envelope, cancellationToken).ConfigureAwait(false);
                if (processed != null)
                {
                    await DispatchToSubscribers(topic, processed, cancellationToken).ConfigureAwait(false);
                }
            }
            finally
            {
                IncrementProcessed();
            }
        }
        else
        {
            // 异步:入队,背压由 Channel 控制
            IncrementEnqueued();
            try
            {
                await queue.Writer.WriteAsync(envelope, cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                // 写入失败则回退计数
                IncrementProcessed();
                throw;
            }
        }
    }

    // 注册中间件
    public void Use(Func<IEvent, Task<IEvent?>> middleware)
    {
        if (middleware is null) throw new ArgumentNullException(nameof(middleware));
        ThrowIfDisposed();
        lock (middlewares)
        {
            middlewares.Add(middleware);
        }
    }

    // 重放主题的粘性事件
    public async Task Replay(string topic, CancellationToken cancellationToken = default)
    {
        if (!enableReplay) return;
        if (string.IsNullOrWhiteSpace(topic)) return;

        IEvent? sticky;
        lock (_stickyLock)
        {
            stickyEvents.TryGetValue(topic, out sticky);
        }
        if (sticky is null) return;

        // 重放按当前模式走(异步分发),不改变原始粘性标志
        IncrementEnqueued();
        try
        {
            await queue.Writer.WriteAsync(sticky, cancellationToken).ConfigureAwait(false);
        }
        catch
        {
            IncrementProcessed();
            throw;
        }
    }

    // 等待队列清空与在途事件处理完毕(用于测试或优雅关闭)
    public Task Flush(CancellationToken cancellationToken = default)
    {
        // 若当前空闲,直接返回
        var tcs = _idleTcs;
        if (IsIdle())
            return Task.CompletedTask;

        // 等待空闲或取消
        return WaitOrCancelAsync(tcs.Task, cancellationToken);
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;

        try
        {
            queue.Writer.TryComplete();
        }
        catch { /* ignore */ }

        try
        {
            _cts.Cancel();
        }
        catch { /* ignore */ }

        try
        {
            _consumerTask.Wait(TimeSpan.FromSeconds(3));
        }
        catch { /* ignore */ }

        _cts.Dispose();

        // 释放所有 handler 资源
        foreach (var kv in subscribers)
        {
            lock (kv.Value)
            {
                foreach (var h in kv.Value.OfType<IDisposable>())
                {
                    h.Dispose();
                }
                kv.Value.Clear();
            }
        }
    }

    // 事务对象(可选使用:将 PublishOptions.Transaction 指定为该对象)
    public sealed class EventBusTransaction : IDisposable
    {
        private readonly EventBus _bus;
        private readonly List<IEvent> _buffer = new();
        private bool _committed;
        private bool _disposed;

        internal EventBusTransaction(EventBus bus)
        {
            _bus = bus;
        }

        public void Enqueue(IEvent evt)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(EventBusTransaction));
            _buffer.Add(evt);
            // 粘性事件在事务阶段也要覆盖保存
            if (_bus.enableReplay && evt.Sticky)
            {
                lock (_bus._stickyLock)
                {
                    _bus.stickyEvents[evt.Topic] = evt;
                }
            }
        }

        public async Task CommitAsync(CancellationToken cancellationToken = default)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(EventBusTransaction));
            if (_committed) return;

            foreach (var evt in _buffer)
            {
                _bus.IncrementEnqueued();
                try
                {
                    await _bus.queue.Writer.WriteAsync(evt, cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    _bus.IncrementProcessed();
                    throw;
                }
            }

            _buffer.Clear();
            _committed = true;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            _buffer.Clear();
        }
    }

    // 若需要:外部可自行为一组 Publish 创建事务并传入 PublishOptions.Transaction
    public EventBusTransaction CreateTransaction() => new EventBusTransaction(this);

    // ========== 内部实现 ==========

    private static TaskCompletionSource<bool> CreateIdleTcs(bool completed)
    {
        var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
        if (completed) tcs.TrySetResult(true);
        return tcs;
    }

    private void IncrementEnqueued()
    {
        var current = Interlocked.Increment(ref _enqueued);
        if (current - Volatile.Read(ref _processed) == 1)
        {
            // 从空闲变为非空闲,重置 TCS
            _idleTcs = CreateIdleTcs(completed: false);
        }
    }

    private void IncrementProcessed()
    {
        var processed = Interlocked.Increment(ref _processed);
        if (processed == Volatile.Read(ref _enqueued))
        {
            _idleTcs.TrySetResult(true);
        }
    }

    private bool IsIdle() => Volatile.Read(ref _enqueued) == Volatile.Read(ref _processed);

    private static async Task WaitOrCancelAsync(Task task, CancellationToken ct)
    {
        using var reg = ct.CanBeCanceled
            ? ct.Register(state => ((TaskCompletionSource<bool>)state!).TrySetCanceled(), new TaskCompletionSource<bool>(), useSynchronizationContext: false)
            : default;

        var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
        using (ct.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false))
        {
            var completed = await Task.WhenAny(task, tcs.Task).ConfigureAwait(false);
            await completed.ConfigureAwait(false);
        }
    }

    private void ThrowIfDisposed()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(EventBus));
    }

    private IEvent CreateEnvelope(string topic, object payload, PublishOptions options)
    {
        var metadata = options.Metadata is null
            ? new Dictionary<string, object?>()
            : new Dictionary<string, object?>(options.Metadata);

        return new EventEnvelope(
            topic: topic,
            payload: payload,
            payloadType: payload.GetType(),
            timestamp: DateTimeOffset.UtcNow,
            sticky: options.Sticky,
            metadata: metadata
        );
    }

    private async Task ConsumeLoop(ChannelReader<IEvent> reader, CancellationToken ct)
    {
        try
        {
            await foreach (var evt in reader.ReadAllAsync(ct).ConfigureAwait(false))
            {
                try
                {
                    var processed = await RunMiddlewares(evt, ct).ConfigureAwait(false);
                    if (processed != null)
                    {
                        await DispatchToSubscribers(processed.Topic, processed, ct).ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException) when (ct.IsCancellationRequested)
                {
                    // shutting down
                }
                catch (Exception ex)
                {
                    logger?.LogError(ex, "EventBus: error while processing event for topic {Topic}", evt.Topic);
                }
                finally
                {
                    IncrementProcessed();
                }
            }
        }
        catch (OperationCanceledException)
        {
            // ignore
        }
        catch (Exception ex)
        {
            logger?.LogError(ex, "EventBus: consumer loop crashed.");
        }
    }

    private async Task<IEvent?> RunMiddlewares(IEvent evt, CancellationToken ct)
    {
        IEvent? current = evt;
        List<Func<IEvent, Task<IEvent?>>> snapshot;
        lock (middlewares)
        {
            snapshot = middlewares.ToList();
        }

        foreach (var mw in snapshot)
        {
            ct.ThrowIfCancellationRequested();
            try
            {
                if (current is null) return null;
                current = await mw(current).ConfigureAwait(false);
                if (current is null) return null; // 拦截
            }
            catch (Exception ex)
            {
                logger?.LogError(ex, "EventBus: middleware failed for topic {Topic}", evt.Topic);
                // 中间件抛错默认终止该事件分发
                return null;
            }
        }

        return current;
    }

    private async Task DispatchToSubscribers(string topic, IEvent evt, CancellationToken ct)
    {
        if (!subscribers.TryGetValue(topic, out var list) || list.Count == 0)
            return;

        IHandler[] handlers;
        lock (list)
        {
            // 拍快照,避免分发时与修改冲突
            handlers = list.ToArray();
        }

        // 并行分发到不同 handler;每个 handler 自身用 Semaphore 控制并发
        var tasks = new List<Task>(handlers.Length);
        foreach (var h in handlers)
        {
            if (!h.EventType.IsAssignableFrom(evt.PayloadType))
                continue;

            tasks.Add(DispatchToSingleHandlerAsync(topic, evt, h, ct));
        }

        if (tasks.Count > 0)
            await Task.WhenAll(tasks).ConfigureAwait(false);
    }

    private async Task DispatchToSingleHandlerAsync(string topic, IEvent evt, IHandler handler, CancellationToken ct)
    {
        await handler.Semaphore.WaitAsync(ct).ConfigureAwait(false);
        try
        {
            await handler.Invoke(evt, ct).ConfigureAwait(false);

            if (handler.Once)
            {
                // 成功后一次性订阅自动移除
                Unsubscribe(topic, handler);
            }
        }
        catch (OperationCanceledException) when (ct.IsCancellationRequested)
        {
            // shutting down
        }
        catch (Exception ex)
        {
            logger?.LogError(ex, "EventBus: handler execution failed on topic {Topic}", topic);
        }
        finally
        {
            handler.Semaphore.Release();
        }
    }

    // Envelope 实现
    private sealed class EventEnvelope : IEvent
    {
        public string Topic { get; }
        public object Payload { get; }
        public Type PayloadType { get; }
        public DateTimeOffset Timestamp { get; }
        public bool Sticky { get; }
        public IReadOnlyDictionary<string, object?> Metadata { get; }

        public EventEnvelope(string topic, object payload, Type payloadType, DateTimeOffset timestamp, bool sticky, Dictionary<string, object?> metadata)
        {
            Topic = topic;
            Payload = payload;
            PayloadType = payloadType;
            Timestamp = timestamp;
            Sticky = sticky;
            Metadata = metadata;
        }
    }

    // Handler 实现(泛型包装 Func<TEvent, Task>)
    private sealed class Handler<TEvent> : IHandler, IDisposable
    {
        private readonly Func<TEvent, Task> _handler;

        public int Priority { get; }
        public int MaxConcurrency { get; }
        public bool Once { get; }
        public Type EventType => typeof(TEvent);
        public Delegate OriginalDelegate => _handler;
        public SemaphoreSlim Semaphore { get; }

        public Handler(Func<TEvent, Task> handler, SubscribeOptions options)
        {
            _handler = handler ?? throw new ArgumentNullException(nameof(handler));
            Priority = options.Priority;
            MaxConcurrency = Math.Max(1, options.MaxConcurrency);
            Once = options.Once;
            Semaphore = new SemaphoreSlim(MaxConcurrency, MaxConcurrency);
        }

        public Task Invoke(IEvent evt, CancellationToken ct)
        {
            // 中间件保证类型匹配,这里做一次防御
            if (evt.Payload is not TEvent typed)
                throw new InvalidOperationException($"Payload type mismatch. Expected {typeof(TEvent).Name}, actual {evt.PayloadType.Name}.");
            return _handler(typed);
        }

        public void Dispose()
        {
            Semaphore.Dispose();
        }
    }

    private sealed class Unsubscriber : IDisposable
    {
        private readonly Action _action;
        private int _disposed;

        public Unsubscriber(Action action) => _action = action;

        public void Dispose()
        {
            if (Interlocked.Exchange(ref _disposed, 1) == 0)
            {
                _action();
            }
        }
    }
}

}

使用示例(简要):

  • 创建与订阅 var bus = new LightweightEventBus.EventBus(enableReplay: true, logger: logger); var d = bus.Subscribe("user.created", async s => Console.WriteLine($"User: {s}"), new SubscribeOptions { Priority = 10, MaxConcurrency = 2, Once = false });

  • 发布(异步) await bus.Publish("user.created", "Alice", new PublishOptions { Sticky = true });

  • 发布(同步,并延迟 100ms) await bus.Publish("user.created", "Bob", new PublishOptions { Synchronous = true, Delay = TimeSpan.FromMilliseconds(100) });

  • 事务性发布 var tx = bus.CreateTransaction(); await bus.Publish("order.paid", new { Id = 1 }, new PublishOptions { Transaction = tx, Sticky = true }); await tx.CommitAsync();

  • 重放与 Flush await bus.Replay("user.created"); await bus.Flush();

  • 释放 d.Dispose(); // 取消订阅 bus.Dispose();

设计说明:

  • 中间件链按注册顺序执行,返回 null 表示拦截该事件。
  • 订阅列表使用 List + lock 确保插入/删除/排序安全;读取时快照数组避免锁持有时间过长。
  • 背压:Channel 为有界队列(默认 1024,满时等待),可通过构造参数调整容量与 FullMode。
  • 粘性存储:按 topic 存最后一次事件;新订阅可立即获粘性事件(可通过 SubscribeOptions 控制)。
  • Flush 通过入队/处理计数对实现,保证异步队列与在途事件处理完成。

示例详情

该提示词已被收录:
“程序员必备:提升开发效率的专业AI提示词合集”
让 AI 成为你的第二双手,从代码生成到测试文档全部搞定,节省 80% 开发时间
√ 立即可用 · 零学习成本
√ 参数化批量生成
√ 专业提示词工程师打磨

解决的问题

帮助开发者快速生成符合面向对象编程规范的类结构,包括属性、方法及可选的设计模式,从而提高开发效率,简化重复性工作,助力软件设计优化。

适用用户

后端开发工程师

需要快速创建标准化类结构,通过提示词轻松实现缩短代码编写时间,提高项目推进速度。

软件架构师

利用提示词设计复杂类与模式,将理论与实践高效结合,为团队提供高质量架构模板。

编程初学者

通过自动化生成类结构,减少学习曲线,为编写高质量代码提供清晰的参考和指导。

特征总结

快速生成面向对象的类结构,囊括属性和方法,优化开发效率。
智能支持多种编程语言的类设计,满足多样化开发需求。
助力实现复杂设计模式,轻松完成工厂、单例等常用模式的集成。
自动化生成结构化代码模板,确保一致性与高质量代码输出。
轻松定制类名、属性和方法,避免重复设计,提升开发体验。
快速添加构造函数和特定属性初始化逻辑,加速项目开发流程。
支持领域特定的个性化类设计,从业务场景需求出发精确建模。
优化代码结构,有助于遵循最佳开发实践,减少手工错误。
创建清晰、可维护的类层次,简化复杂系统的架构设计。

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

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

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

2. 发布为 API 接口调用

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

3. 在 MCP Client 中配置使用

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

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

您购买后可以获得什么

获得完整提示词模板
- 共 113 tokens
- 5 个可调节参数
{ 编程语言 } { 类名称 } { 类表示概念 } { 属性信息 } { 方法信息 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
使用提示词兑换券,低至 ¥ 9.9
了解兑换券 →
限时半价

不要错过!

半价获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59