当前位置:首页 > 焦点

系统软件常见业务逻辑

用户点下“确认支付”按钮的系统那一刻,系统后台至少发生了七件事。软件扣减库存、业务生成订单、逻辑调用支付网关、系统写入流水、软件更新用户积分、业务发送通知、逻辑触发风控复查。系统这些动作不是软件同时发生的,顺序错了会超卖,业务漏掉一个用户就会收到“支付成功但订单消失”的逻辑投诉。系统软件的系统业务逻辑,说到底就是软件把这些动作按正确顺序串起来,并且在中间任何一步失败时,业务知道该退回到哪里。

最常见的逻辑是“状态机”。订单从待支付到已支付,再到已发货、已完成,每一步都有前置条件。你不能把待支付的订单直接标记为已发货,就像你不能让一个没登机的乘客直接进入到达大厅。我见过一个电商系统,开发图省事,用布尔值 is_paid 和 is_shipped 两个字段来管理订单。结果客服可以把未支付的订单点成已发货,仓库那边真的把货发了出去,月底对账才发现有三十多单没收到钱。状态机不是理论洁癖,它是业务安全的底线。

另一个绕不开的是幂等。用户手抖点了两次提交,或者网络超时后客户端自动重试,系统不能因此扣两次款。做法通常是在数据库里放一个唯一键,比如 request_id,第一次写入成功,第二次直接返回已存在的结果。听起来简单,但很多团队是在线上出了重复扣款事故之后才补上这一层。我印象很深的一个案例:某 SaaS 产品的续费接口没有做幂等,一个客户因为浏览器卡顿连点了五次,被扣了五个月的费用。技术团队后来复盘,发现代码里连最基础的 SELECT ... FOR UPDATE 都没用。

事务边界也是容易踩坑的地方。不是所有操作都能塞进一个数据库事务里。调用外部支付接口、发短信、推消息队列,这些动作一旦跨出本地事务,就必须考虑补偿。比如订单创建成功了,但发短信通知失败了,你不能把订单回滚——用户已经看到支付成功了。正确做法是记录一条“待发送通知”的任务,让后台重试。业务逻辑里,“最终一致”比“强一致”更常见,也更难写对。强一致靠数据库,最终一致靠人的耐心和代码里的重试机制。

权限校验经常被写在业务逻辑的夹缝里。一个用户能不能取消别人的订单?能不能修改已发货订单的地址?这些判断如果散落在各个接口里,迟早会漏。我倾向于把权限规则抽成独立的策略对象,比如 OrderPolicy.canCancel(user, order),业务代码只调用这个策略,不自己写 if。这样改规则的时候只改一个地方,测试也好写。

还有一类逻辑是“时间窗口”。优惠券过期、秒杀开始、账单出账日,这些逻辑依赖系统时间。但服务器时间可能不准,不同节点的时间也可能有毫秒级偏差。我见过一个秒杀系统,因为两台应用服务器时间差了 300 毫秒,导致一部分用户在活动开始前 200 毫秒就抢到了商品。后来他们改成用 Redis 的 TIME 命令统一取时间,才解决这个问题。业务逻辑里,时间不是你以为的那个时间。

写业务逻辑的人,大部分时间不是在写新功能,而是在处理边界情况:空值、重复请求、并发冲突、外部服务超时、部分成功。把这些情况想清楚,代码自然就稳了。想不清楚,上线之后用户会帮你逐个发现。区别只在于,是你在监控面板上先看到,还是用户在微博上先吐槽。

分享到: