在很多金融类产品里,下单模块几乎永远是最复杂的前端代码之一。
一个完整的交易流程通常包括:
- 普通单 / 条件单
- 创建订单 / 改单
- 价格校验
- 资产校验
- 风险提示
- 协议签署
- 二次确认
- 输入交易密码
- 提交订单
- 结果处理
随着需求不断叠加,很多项目最后都会演变成这样:
ts
async function onOrder() {
if (!priceValid()) return toast("价格不合法")
if (!assetEnough()) return toast("资产不足")
if (needRiskConfirm()) {
await openRiskDialog()
}
if (!signedAgreement()) {
await openAgreementDialog()
}
if (!await openConfirmDialog()) return
const password = await openPassword()
const res = await submitOrderApi()
handleResult(res)
}
组件文件很容易膨胀到:
ts
Order.vue
≈ 2000 行
随着业务增长,会出现三个典型问题:
1️⃣ 校验逻辑越来越复杂
2️⃣ 创建单 / 改单到处是 if (isEdit)
3️⃣ 提交流程越来越长
后来我对这块代码做了一次系统性的重构,最终形成了一套 稳定可扩展的交易前端架构:
ts
OrderSession
+ OrderFlowStateMachine
+ RuleEngine
+ SubmitPipeline
+ Ports / Adapters
下面把这套架构完整拆解。
一、交易系统为什么容易失控
交易模块有一个典型特点:
流程很长 + 规则很多。
完整流程通常是:
ts
填写订单
↓
前置校验
↓
风险确认
↓
协议签署
↓
最终确认
↓
输入密码
↓
提交订单
↓
结果处理
如果所有逻辑都写在组件里,代码结构会变成:
ts
UI + 业务规则 + 流程控制 + API调用
全部混在一起。
解决办法只有一个:
拆层。
二、交易前端架构分层
重构后我们把交易模块拆成五层:
ts
Presentation
↓
Application
↓
Domain
↓
Ports
↓
Adapters
整体结构如下:
ts
UI (Vue / React)
↓
useOrderFlow
↓
OrderFlowMachine
↓
SubmitPipeline
↓
RuleEngine
↓
OrderSession
↓
Ports
↓
Adapters(API / Store)
每一层职责都非常清晰。
三、OrderSession:统一创建单和改单
交易系统中有一个非常经典的问题:
ts
创建订单
改单
很多项目的代码会变成:
ts
if (isEdit) {
// 修改逻辑
} else {
// 创建逻辑
}
时间久了,整个项目会充满:
ts
if (isEdit)
解决方案是引入一个领域模型:
OrderSession
ts
interface OrderSession {
type: "CREATE" | "EDIT"
entrustId?: string
originOrder?: OrderDTO
}
它表示:
用户的一次下单会话。
例如:
ts
CREATE Session → 创建订单
EDIT Session → 修改订单
所有逻辑只依赖 session.type,而不是 isEdit。
四、RuleEngine:把校验逻辑规则化
原来的校验代码通常是:
ts
if (!priceValid()) return toast()
if (!assetEnough()) return toast()
if (priceDiffTooLarge()) openRiskDialog()
随着需求增加,preVerify 会变成一大坨 if。
解决方案是 规则引擎化。
定义统一结果结构:
ts
interface ValidationResult {
code: string
severity: "block" | "warn"
message: string
}
规则写成纯函数:
ts
function priceRule(ctx): ValidationResult | null
function assetRule(ctx): ValidationResult | null
统一执行:
ts
function runRules(ctx, rules) {
return rules
.map(rule => rule(ctx))
.filter(Boolean)
}
新增规则只需要:
ts
新增一个函数
而不需要修改原有代码。
五、SubmitPipeline:拆解提交流程
原来的提交流程通常是:
ts
verify
↓
risk confirm
↓
confirm
↓
password
↓
submit
我们把它拆成 Pipeline:
ts
ValidateStep
↓
RiskConfirmStep
↓
AgreementStep
↓
FinalConfirmStep
↓
PasswordStep
↓
SubmitStep
↓
ResultStep
代码实现:
ts
async function runPipeline(ctx, steps) {
for (const step of steps) {
await step(ctx)
}
}
好处是:
流程步骤可以自由插拔。
例如新增:
ts
冷静期确认
只需要增加一个 step。
六、OrderFlowStateMachine:状态机驱动流程
交易流程本质上是一个 状态机。
定义状态:
ts
FORM
VALIDATING
RISK_CONFIRM
PASSWORD
SUBMITTING
SUCCESS
FAILED
状态转移:
ts
FORM
↓
VALIDATING
↓
RISK_CONFIRM
↓
PASSWORD
↓
SUBMITTING
↓
SUCCESS
简单实现:
ts
class OrderFlowMachine {
state = "FORM"
transition(next) {
this.state = next
}
}
UI 只根据状态渲染:
ts
FORM → 表单
RISK_CONFIRM → 风险弹窗
PASSWORD → 密码输入
逻辑会变得非常清晰。
七、Ports / Adapters:隔离外部系统
交易模块通常依赖很多系统:
ts
行情
资产
交易接口
协议系统
如果直接依赖 Store 或 API,会导致代码强耦合。
解决方案是 Ports + Adapters。
定义接口:
ts
interface OrderPort {
submitOrder(command)
}
Adapter 实现:
ts
export const orderAdapter = {
submitOrder(cmd) {
return api.trade.submit(cmd)
}
}
Domain 只依赖:
ts
Port
而不是具体 API。
八、完整目录结构
重构后的目录结构大致如下:
ts
order/
domain/
OrderSession.ts
OrderContext.ts
validation/
priceRule.ts
assetRule.ts
modifyRule.ts
application/
OrderFlowMachine.ts
submitPipeline.ts
ports/
OrderPort.ts
MarketPort.ts
adapters/
orderAdapter.ts
marketAdapter.ts
composables/
useOrderFlow.ts
结构非常清晰。
九、改造后的效果
重构前:
ts
Order.vue
≈ 2000 行
重构后:
ts
UI组件
≈ 300 行
其余逻辑全部放在:
ts
Domain
Application
新增规则或流程时:
ts
新增 Rule
新增 Step
而不是修改原有代码。
十、总结
复杂交易系统的稳定架构通常由五个核心模块组成:
ts
OrderSession
+
StateMachine
+
RuleEngine
+
SubmitPipeline
+
Ports / Adapters
简单理解就是:
ts
Session 管数据
StateMachine 管流程
RuleEngine 管规则
Pipeline 管步骤
Ports 管依赖
当这些模块拆清楚之后,下单系统就会变成:
稳定、清晰、可扩展。
这也是很多大型金融系统前端常见的架构模式。