网站业务逻辑
用户点下“提交订单”那一秒,网站后台至少跑了十七件事。业务扣库存、逻辑算优惠、网站锁座位、业务发短信、逻辑写日志——任何一环卡住,网站页面就转圈。业务网站业务逻辑不是逻辑流程图,是网站这些动作的先后顺序和失败预案。
去年帮一个做演出票务的业务朋友看系统。他们最怕的逻辑不是流量大,是网站“超卖”。两个用户同时点同一张票,业务代码里如果先查库存再扣减,逻辑中间隔了50毫秒,第二个人就会看到“有票”,付完钱才发现没座位。解决办法不复杂:把“查”和“扣”合并成一个原子操作。数据库里写UPDATE stock SET count=count-1 WHERE count>0,返回影响行数为0就直接报错。这比任何分布式锁都干脆。
但业务逻辑的麻烦在于,规则总在变。今天老板说“VIP打八折”,明天运营要加“满三张送饮料”。代码里如果到处写if (user.isVip) price *= 0.8,三个月后没人敢动。我见过一个电商项目,优惠计算散落在控制器、服务层、甚至前端JS里。改一个活动,测试要回归二十个页面。后来他们抽出“价格引擎”,把每种优惠做成独立规则,按优先级串成链。新需求来了,加个类就行,老逻辑不动。
状态管理是另一个坑。订单从“待支付”到“已支付”再到“已发货”,中间可能插入“退款中”“部分发货”。如果只用status字段存数字,代码里全是if (status == 3 && !refundFlag)这种判断。更稳的做法是用状态机:定义好每个状态能触发哪些事件,事件发生后只能跳到指定状态。比如“待支付”只能由“支付成功”事件变成“已支付”,不能直接跳到“已发货”。这样即使运营手动改数据,也会被状态机拦住。
还有一类逻辑容易被忽略:幂等。用户网络卡了,连点三次“提交”,后台不能生成三个订单。常见方案是前端生成一个requestId,服务端用Redis存这个ID,处理过就直接返回上次结果。但要注意,如果第一次请求超时了,第二次带着同样的ID来,你得判断是“正在处理”还是“已处理完”。我倾向于让客户端轮询一个查询接口,而不是盲目重试。
说个反例。某社交App的“点赞”功能,早期逻辑是:先查用户是否点过,没点过就插入记录,然后更新总数。上线后偶尔出现总数比实际记录多。原因是有并发请求同时通过了“查是否点过”这步。后来改成唯一索引+INSERT IGNORE,总数用定时任务重算。业务逻辑不追求绝对实时,但必须最终一致。
写业务代码的人常有一种错觉:觉得逻辑简单,随便写写就行。实际上,真正难的不是实现功能,是处理边界。库存刚好扣到0怎么办?优惠券过期前一秒下单算不算?用户支付成功但回调丢了怎么补?这些场景不会写在需求文档里,但线上一定会发生。我的习惯是,每写一个分支,就问自己:如果这步失败了,数据会脏吗?能重试吗?用户看到什么?
最后说个观察。很多团队把业务逻辑写在存储过程或触发器里,觉得“离数据近,性能好”。短期确实快,但半年后没人敢改。调试要连数据库,版本管理靠导出SQL文件,新人上手成本极高。除非是极高频的简单操作,否则还是放在应用层。用代码表达规则,用测试覆盖分支,用日志记录决策。网站业务逻辑的终点,是让下一个改代码的人能看懂你为什么这么写。









