稳过版回答稿

2026-04-06
6
-
- 分钟

这套稿子不是让你背术语,是让你在面试里能稳定落到代码。

项目总述
这个项目是一个基于 DDD 思路拆分的拼团交易系统,技术上用的是 Spring Boot、MyBatis、MySQL、Redis 和 Redisson,按职责拆成了 api、app、domain、infrastructure、
trigger、types 六个模块。
我自己重点参与和能讲透的部分是三条链路:营销试算、交易锁单、支付结算,以及一个配套的 DCC 动态配置组件和本地消息表通知机制。

1 分钟自我介绍版
这个项目核心是把拼团交易从活动配置、优惠试算、开团参团锁单到支付后结算通知串成闭环。
我主要做的是交易主链路的拆分和部分关键组件实现,比如锁单规则链、结算通知、本地消息表补偿,以及基于 Redis Pub/Sub 的轻量级 DCC 动态配置。这个项目里我比较重视
两件事,一是把复杂业务拆成可扩展的领域服务,二是把高并发场景下的幂等、限次、最终一致性这些问题提前收进去。

项目架构怎么讲
我会把它讲成三层,不要乱说“微服务”,当前仓库更像多模块单体。

  1. trigger
    负责 HTTP 入口和定时任务,比如交易接口和通知补偿 job。
  2. domain
    放核心业务逻辑。
    activity 负责试算。
    trade 负责锁单和结算。
    tag 负责人群标签。
  3. infrastructure
    负责 DAO、Redis、外部通知这些适配。

你可以补一句:
我这里是用模块边界隔离业务,不是简单按 controller/service/dao 平铺。

高频题标准答法

  1. 你这个项目的锁单链路怎么走?

推荐回答:

lock_market_pay_order 先从 Controller 进来,第一步做必填参数校验,比如 userId、source、channel、goodsId、activityId、notifyUrl。
第二步会先查未支付订单,按 userId + outTradeNo 判断是否已经存在未支付锁单记录,这一步本质上是做幂等保护,避免重复提交导致重复锁单。
第三步如果这次是参团而不是开团,还会根据 teamId 查当前团进度。如果已经达到锁单目标,就直接拦截,不再继续占座。
然后进入试算服务,调用 indexMarketTrial,根据用户、渠道、商品、活动拿到当前活动优惠、商品价格和人群可见性结果。
如果试算结果里用户不可见或者不可参与,就直接返回。
试算通过后,会组装三个核心实体传给锁单领域服务:UserEntity、PayActivityEntity、PayDiscountEntity。
其中活动名称、开始结束时间、有效时长、成团目标人数来自活动优惠对象;商品原价、优惠金额、支付金额、外部单号、通知地址来自试算结果和请求参数。
进入 TradeLockOrderService 后,会先走一遍责任链规则校验,拿到用户已参与次数 userTakeOrderCount,然后把这些数据组装成 GroupBuyOrderAggregate,最后交给
repository 在事务里完成锁单相关数据落库。
我理解 repository 层至少会涉及营销支付单、团队占座或参团关联数据,以及用户参与次数相关约束。

你一定要记住几个字段来源:
activityName/startTime/endTime/validTime/targetCount 来自试算后的活动优惠对象。
goodsName/originalPrice/deductionPrice/payPrice/outTradeNo/notifyUrl 来自试算结果和请求参数。
userTakeOrderCount 不是试算给的,是责任链返回的。

  1. 为什么要先查未支付订单?

推荐回答:

这是典型幂等保护。锁单接口在真实场景里可能被前端重复提交、网络重试、用户多次点击触发。如果不先查未支付订单,同一个 outTradeNo 可能生成多条未支付记录,后面会
把库存占座、团进度、支付状态都搞乱。所以这里先查未支付记录,查到就直接返回已有结果,不重复创建。

  1. 责任链到底做了什么?

推荐回答:

锁单服务不是直接落库,而是先过交易规则链。当前工厂里已经接了两个节点,一个是活动可用性校验,一个是用户参与次数限制校验。
命令对象 TradeLockRuleCommandEntity 负责带本次请求必要参数,比如活动 ID、用户 ID。
上下文 DynamicContext 用来在多个节点之间共享中间态,当前至少放了 groupBuyActivity,这样前一个节点查出来的活动信息后面的节点还能复用,避免重复查库。
我用责任链而不是一个大 if-else,主要是因为后续规则会持续增加,比如渠道黑名单、设备风控、白名单活动等。拆成独立节点以后新增规则只需要加节点和调整装配顺序,不
用改原有大段逻辑,更符合开闭原则,也更利于单测。

  1. 如果新增渠道黑名单规则,你怎么做?

推荐回答:

我会新增一个 ChannelBlacklistRuleFilter,位置放在锁单责任链这层,也就是 domain.trade.service.lock.filter 这一组节点里。
然后在 TradeRuleFilterFactory 里把它作为新的 bean 接到链上。
顺序上我会优先放前面,因为渠道黑名单属于低成本、强拦截的校验,应该尽早失败,减少后面活动查询和用户次数校验的开销。
这样改不会破坏已有链路,因为原有节点不需要改,只是扩展了链的装配顺序。

  1. 本地消息表在这里怎么用?

推荐回答:

它出现在支付结算之后的异步通知场景。
settlementMarketPayOrder 负责支付成功后的结算,它先走结算规则链,拿到团队和订单相关信息,组装聚合对象后进入 repository 做结算。
我对这里的理解是:repository 在事务里除了更新支付单、团队进度这些业务数据外,还会在需要通知外部系统时写入 notify_task 这类本地消息记录。
事务提交成功后,服务层会根据 isNotify 判断当前是否需要立即通知。如果需要,会直接按当前 teamId 执行一次通知任务,相当于主流程里做一次快速投递。
如果这次通知失败,或者当时没有处理成功,后面再由定时任务扫描未执行任务做补偿。
所以这里不是“只有 job 补偿”,而是“主流程即时通知 + 定时任务兜底补偿”。

  1. isNotify 代表什么?

推荐回答:

从代码语义看,它更像“当前结算结果是否已经满足通知条件”,而不是“只要支付成功就通知”。
因为不是每次支付都会触发外部发货,通常应该是成团或满足某个业务状态后才需要通知。这里 repository 结算后返回一个布尔值,再决定要不要立即通知,说明它承载的是业
务状态判断结果。

  1. 你怎么看这套通知状态流转?

推荐回答:

思路是对的,但当前命名我会留一个保守意见。
现在成功时会更新 success,失败时如果 notifyCount < 5 调一个 updateNotifyTaskStatusError,超过阈值再调 updateNotifyTaskStatusRetry。
从名字看,error 和 retry 的语义容易让人误解,像是反过来的。
如果是我继续维护,我会把状态定义得更清晰,比如 WAITING、RETRYING、SUCCESS、DEAD,然后把重试次数和终态条件独立出来,这样别人一看就知道状态机怎么跑。

  1. DCC 组件怎么讲?

推荐回答:

这个组件的目标是做轻量级动态配置,不想每次改开关都重启应用。
实现上,DCCValueBeanFactory 既是配置类也是 BeanPostProcessor。
在 Bean 初始化完成后,它会扫描 bean 里的字段,如果字段上标了 @DCCValue,就解析出配置 key 和默认值,然后从 Redis 读取已有值,没有就写默认值,最后把值反射设置
到目标 bean 上。
之所以要处理 AOP 代理,是因为很多 Spring bean 最终拿到的是代理对象,如果不剥离代理,可能拿不到真实业务类上的字段。
同时它还会维护一个本地映射,把配置 key 对应到具体 bean。
当 Redis Topic 收到配置变更消息时,它会解析消息内容,先更新 Redis 中的 bucket,再根据 key 找到 bean,通过反射直接改字段值,实现运行时热更新。

  1. 这套 DCC 有什么风险?

这题一定别说“没问题”。

推荐回答:

这套实现思路能跑通,但工程上还有几个明显风险。
第一,字段赋值目前看起来默认按字符串反射写入,如果字段类型是 Boolean、Integer 甚至复杂类型,会有类型转换问题。
第二,本地缓存用的是 HashMap,如果初始化和消息监听并发发生,线程安全不够稳。
第三,消息格式校验比较弱,直接 split 后按下标取值,脏消息可能直接抛异常。
第四,监听线程里异常是直接抛运行时异常,隔离不好,可能影响后续消息处理。
第五,配置一致性比较弱,目前更像最终覆盖,本地更新和 Redis 更新之间没有版本控制。
如果我继续演进,我会加类型转换器、改成并发容器、补消息格式校验和日志告警,再加版本号或时间戳控制配置覆盖顺序。

你现在最该背熟的 6 个点

  1. 锁单链路顺序。
  2. 三个实体和一个聚合对象分别是什么。
  3. 责任链当前有哪些节点。
  4. userTakeOrderCount 从哪来。
  5. 即时通知和 job 补偿的区别。
  6. DCC 的 4 个风险点。

面试时的人设建议
别再把自己说成“后端负责人”。
更稳的表达是:

在这个项目里,我主要负责交易主链路相关实现,重点参与了锁单规则链、结算通知和 DCC 组件这几块。对整体架构我能讲清,但最熟的是交易和配置这两个模块。

这句话比“负责人”安全很多,也更像真实实习生。

评论交流

文章目录