热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
本提示词可根据用户输入生成完整类结构,包括属性、方法及可选构造函数与设计模式,实现面向对象高效建模,适用于前端组件化开发和快速类设计。
// 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
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
// 先跑一次同步,若失败则短路整个异步流程
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.*;
/**
购物车聚合根:管理购物项、优惠与结算,保证金额精度与货币一致性。
// 基础配置 private final Currency currency; // 购物车货币(单一) private final MathContext mathContext; // 金额计算精度 private final RoundingMode roundingMode; // 金额舍入规则 private final int moneyScale; // 金额小数位(通常 2) private final Clock clock; // 可注入时钟,便于测试
// 聚合内数据
private final List
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
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(); }
/**
/**
/**
/**
/**
/**
结算:生成订单草案、锁定库存并返回应付信息。
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 static Builder builder(Currency currency) { return new Builder().currency(currency); }
// ======================= 内部计算/校验 =======================
private Optional
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
// 可选:动态更换运费策略(例如结算时基于地址) 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
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
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
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
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
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
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), "会员折扣"); } }
/**
下面给出一个可直接复制使用的 C# 实现,包含 EventBus 类及其所需的配套接口/选项类型。该实现特点:
说明:
代码如下:
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
发布(异步) 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();
设计说明:
帮助开发者快速生成符合面向对象编程规范的类结构,包括属性、方法及可选的设计模式,从而提高开发效率,简化重复性工作,助力软件设计优化。
需要快速创建标准化类结构,通过提示词轻松实现缩短代码编写时间,提高项目推进速度。
利用提示词设计复杂类与模式,将理论与实践高效结合,为团队提供高质量架构模板。
通过自动化生成类结构,减少学习曲线,为编写高质量代码提供清晰的参考和指导。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
半价获取高级提示词-优惠即将到期