核心交易逻辑中拦截非法金额应通过结构化、可插拔的流程控制实现,而非嵌套if判断;具体分为parse→normalize→validate→enforce四阶段,各阶段职责单一,失败时抛出带phase、ruleid、rawinput等上下文的自定义异常,并由全局处理器按phase分流响应,同时流程上下文自动绑定审计日志。

在核心交易逻辑里拦截非法金额,关键不是靠层层 if 判断堵漏洞,而是用流程控制把校验环节结构化、可插拔、可追踪,再让自定义异常成为这个流程的“语义出口”——既不打断主干逻辑,又能精准传递问题本质和上下文。
把金额校验变成可编排的流程节点
不要把金额校验写死在 service 方法里。应抽象为独立的流程步骤,例如:parse → normalize → validate → enforce。每个步骤职责单一,失败时统一交由流程引擎决定是否中断或跳过。
- parse:将原始输入(字符串/JSON 字段)转为 BigDecimal,并记录 raw_input 和类型信息
- normalize:按币种补零、四舍五入(如 JPY 截断小数),并标记是否发生修正
- validate:调用规则集合(MinAmountRule、MaxAmountRule 等),逐条执行
- enforce:触发风控联动(如临时冻结、限流计数),仅在 validate 通过后执行
异常只在流程断点处抛出,且必须带执行路径
流程中任意一步失败,不直接 return false 或 log 后忽略,而是抛出带语义的自定义异常,且该异常必须包含当前所处流程阶段(phase)、触发规则(ruleId)、原始输入(rawInput)和时间戳。
- 例如:throw new InvalidAmountException("PARSE_FAILED", "100.00001", "JPY", Instant.now())
- 避免在 parse 阶段抛出 MaxAmountRule 相关信息——那属于 validate 阶段的责任
- 全局处理器根据 phase 字段自动分流:PARSE_FAILED 走格式修复通道;VALIDATE_FAILED 走风控审计通道
流程控制层统一捕获与降级响应
在流程入口(如 @Transactional 方法顶层)用 try-catch 包裹整个流程链,但 catch 的不是 Exception,而是明确的业务异常类型,实现“失败即响应”,不污染主干分支。
- 捕获 InvalidAmountException → 返回标准错误体 {code: "AMT_PARSE_ERR", msg: "金额格式不合法", trace_id: "..."}
- 捕获 HighRiskAmountException → 触发异步风控动作(如调用风控服务标记账户),同时返回用户友好提示
- 不 catch RuntimeException —— 那是系统级问题,应由 ThreadGroup.uncaughtException 统一兜底
流程状态与异常快照自动绑定
流程启动时生成不可变上下文(AmountContext),包含 account_id、currency、trace_id、start_time 等字段;每一步执行结果(成功/失败、耗时、输出值)都追加到该上下文;异常抛出时,自动将完整上下文注入异常对象。
- AmountContext 提供 toAuditLog() 方法,输出 JSON 化字段,供 ELK 实时聚合分析
- 日志中天然带 transaction_id、phase、rule_id、raw_input、normalized_value,无需手动拼接
- 同一笔请求的所有日志通过 trace_id 关联,还原完整拦截路径











