怎么用 这是格式与深度的参考,不是让你照抄的模板。数字全部是示例,直接抄进简历一问就穿——必须换成你自己的真实数据,而且要能说清是怎么测出来的。下面每个模块都有「数字是怎么测的」和「面试追问」两段,写不出那两段的模块就不要往简历上写

面试题库(电商 / 短视频 / 社区 / 本地生活 / 旅游 / 企业服务 多业务场景)

实习级这一档是干什么的 智能派单、履约调度、动态定价这些核心台面,实习生大概率碰不到。这一档收的是商家侧支撑里实习生真的会被分派、但仍然有明确技术含量的活。下面三个模块彼此独立——只做过一个就只写一个。
怎么讲才不显得 low 「我做了个资质审核,商家上传、运营点通过」和「资质有有效期,营业执照到期了这家店还在正常接单——而没有任何用户操作会触发这个状态变化,必须有定时任务去扫;扫到之后是直接下线还是先提醒,取决于误判的代价」——同一件事,后者面试官会顺着追问。这一档的破解办法是找到「时间到了状态该变,但没人会来触发它」这类问题:资质到期、公告生效失效、评价可回复期,本质上是同一类。
三条自检 一、能说出不这么做会怎样(资质过期不扫会让无证门店继续营业、公告同步循环下发会打挂接口、评分实时聚合会拖慢门店列表);二、能说出你踩过的具体坑;三、能说出量级(多少门店、一次公告发多少家、日均多少条评价)。三条都有就能写。
项目背景设定 本地生活平台的商家侧后端,Spring Boot + MySQL + Redis + 消息队列。我负责的是商家入驻资质、平台公告下发、评价与商家回复这三块「不在履约主链路上、但每天都在跑」的功能。
为什么这三块值得写 它们共同的隐藏难点是时间维度的状态变化:资质会过期、公告有生效与失效时间、评价有可回复期限。这类状态变化没有任何用户操作会触发它,必须由定时任务主动去扫——而这正是新手最容易漏的一类需求(功能测试全部通过,上线几个月后才暴露)。另外两个共性是批量下发不能同步循环聚合数据不能实时算,都是量级上来后的必然改动。

模块一:商家资质审核与有效期治理

  1. 商家资质审核与有效期治理(驳回原因结构化 + 证件号一证多店冲突检测 + 到期前分级提醒与到期后降级 + 材料访问的权限与留档)★★★
    简历这样写 商家资质审核(Spring Boot + 状态机 + 定时扫描 + 对象存储私有读):把审核状态收敛成显式状态机(待提交/待审核/已通过/已驳回/已过期),驳回改为结构化原因(字段级定位 + 分类编码 + 说明),商家可只补对应材料而非整套重提;补充证件号唯一性校验,识别同一营业执照被多个门店重复使用的情况并转人工核查;资质有效期原先无人跟踪,新增到期前分级提醒(多个时间点递进)与到期后自动降级(先限制接单而非直接下线,保留申诉窗口);资质材料含敏感信息,改为私有存储加时效访问凭证并记录每次查看的操作人与时间。上线后过期资质门店不再持续营业,驳回后的重复提交次数明显下降。
    展开完整拆解
    为什么要这么设计

    资质审核第一版的实现很直接:商家上传几张图、状态置为「待审核」、运营点「通过」或「驳回」。功能测试全过,上线之后四个问题陆续暴露,而且都不是功能 bug。

    一是驳回之后商家反复提交。驳回原因是一个自由文本框,运营写「材料不清晰」。商家不知道是哪一张不清晰,只能把全部材料重拍一遍再交。再被驳回,再重拍。有的商家来回提交了七八次——每一次都要占用一次审核人力。

    二是同一张营业执照被多个门店重复使用。我完全没做证件号的唯一性校验。结果是有人用一张执照开了十几家门店,其中一部分是明显的违规操作。这个问题是风控同学发现的,不是我们自己发现的。

    三是资质过期完全没人管。营业执照、食品经营许可证都有有效期,我把有效期存下来了,但没有任何逻辑去检查它上线几个月后有一批门店的许可证已经过期,还在正常接单——这是合规风险。问题的本质是:到期这件事没有任何用户操作会触发它,商家不会主动来说「我的证过期了」。

    四是资质材料的访问没有任何控制。图片直接存在公开可读的地址上,拿到链接的人都能看到营业执照、法人身份证。而且谁看过、什么时候看的,完全没有记录。

    所以四个改动:状态收敛成显式状态机驳回原因结构化到字段级证件号唯一性校验加冲突转人工到期前分级提醒加到期后降级材料改私有存储加时效凭证并记录访问

    这个模块最想说的一句话是:「时间到了状态应该变化」这类需求,是功能测试永远发现不了的。提交、审核、驳回这些都有用户操作来触发,测试点一遍就能覆盖;而「有效期到了要处理」没有任何操作会触发它,只有靠定时任务主动去扫。我漏掉它不是因为技术难,是因为我的思维停留在「用户会做什么」,没想到「时间会做什么」。

    整体链路
    状态机(先把状态收敛,否则后面全乱) │ ├─ 待提交 → 待审核 → 已通过 / 已驳回 │ 已驳回 → 待审核(补充材料后重提) │ 已通过 → 即将到期 → 已过期 │ ├─ 每个流转都记录:操作人 · 时间 · 前后状态 · 原因 │ 不记录的后果:商家问「我什么时候被驳回的」查不到 │ └─ 状态只能按图流转,非法流转直接拒绝并告警 散落在各处的 if 判断会让状态变成不可枚举的组合 提交与校验 │ ├─ 格式校验:图片清晰度 · 大小 · 必填项 · 有效期格式 │ 能机器判的先机器判,减少人工审核的无效工作量 │ ├─ 证件号唯一性校验(这一条我最初完全漏了) │ 同一营业执照号已被其他门店使用 → 不直接拒,转人工核查 │ 因为连锁品牌确实存在一照多店的合法情况 │ └─ 有效期必填且必须晚于当前时间 过期的证不该被受理 —— 提交时就该拦住 审核与驳回 │ ├─ 驳回原因结构化三段 │ 定位到具体字段(哪一张材料 / 哪一个填写项) │ 分类编码(模糊不清 / 信息不一致 / 已过期 / 类型错误) │ 补充说明(可选,承载个案细节) │ ├─ 商家只需补对应材料,其余材料保留 │ 整套重提的后果:来回七八次,每次都占审核人力 │ └─ 驳回原因分类要能统计(哪类问题最多 → 改前端提示或示例图) 有效期治理(没有任何用户操作会触发它,必须主动扫) │ ├─ 定时任务扫描即将到期的资质 │ 分级提醒:提前较长时间一次 · 临近再一次 · 到期当天一次 │ 只发一次的后果:商家看漏就过期了 │ ├─ 提醒渠道多路(站内 + 短信 + 商家后台顶部横幅) │ 横幅是最有效的 —— 他每天都会登录后台 │ ├─ 到期后不直接下线,先降级 │ 限制接单 / 隐藏门店,但保留登录与申诉窗口 │ 直接下线的风险:万一有效期录错了,误伤一家正常营业的店 │ └─ 补交并审核通过后立即恢复 恢复要幂等 —— 可能被多次触发 材料的访问控制(含敏感信息) ├─ 私有存储,不可公开读 ├─ 查看时签发时效访问凭证(短有效期,用完即失效) ├─ 每次查看记录操作人 · 时间 · 查看了哪份材料 └─ 下载与批量导出需要更高权限并单独留档 可观测 ├─ 各状态的门店数量与停留时长(发现审核积压) ├─ 驳回原因分类分布(发现前端引导不足的环节) └─ 即将到期与已过期数量(合规视角的核心指标)
    分步拆解
    1. 先把状态收敛成显式状态机,这是其他改动的前提。散落在各处的 if 判断会让状态变成不可枚举的组合,后面加「即将到期」「已过期」时会到处漏改。
    2. 每次状态流转都要记录操作人、时间、原因。商家会问「我什么时候被驳回的、谁驳的、为什么」,不记录就答不上来,只能靠运营回忆。
    3. 非法流转要直接拒绝并告警,不要静默容忍。静默容忍的后果是数据里出现不该存在的状态,而你不知道是哪个入口造成的。
    4. 能机器判的先机器判。图片大小、清晰度、必填项、有效期格式。把这些明显问题挡在提交环节,能省掉大量无效的人工审核。
    5. 证件号唯一性必须校验,但冲突不等于违规。连锁品牌确实存在一照多店的合法情况。所以做法是「转人工核查」而不是「直接拒绝」——直接拒绝会挡住正常的连锁商家。
    6. 有效期必须在提交时就校验(不能早于当前时间)。已经过期的证不该被受理,在提交环节拦住比审核时发现更省事。
    7. 驳回原因要定位到具体字段,这是减少重复提交的关键。「材料不清晰」商家不知道是哪张,只能全套重拍;「营业执照照片模糊」他就只补那一张。
    8. 驳回后其余材料要保留,只补被驳的部分。要求整套重提是把成本转移给了商家,而重提的每一次都会再占用一次审核人力,最终成本还是回到我们身上。
    9. 驳回原因的分类编码要能统计。某一类原因占比很高,说明前端的填写引导或示例图有问题——这是从源头减少驳回的依据。
    10. 有效期治理必须靠定时任务主动扫描。这类状态变化没有任何用户操作会触发它。这是我漏掉的那个需求,也是这个模块最值得讲的一点。
    11. 提醒要分级、要多次。只在到期前发一次,商家看漏就过期了。提前较长时间、临近、到期当天各一次,递进提醒。
    12. 商家后台顶部横幅是最有效的提醒渠道。短信可能被忽略、站内消息可能不看,但他每天都会登录后台处理订单。选渠道要看用户的真实行为习惯。
    13. 到期后先降级不直接下线。限制接单但保留登录与申诉窗口。因为有效期可能录错(人工录入或识别错误),直接下线会误伤正常营业的门店——而门店停业一天的损失是实打实的。
    14. 补交通过后的恢复要幂等。恢复动作可能被多次触发(重试、重复审核),不幂等会产生重复的通知或状态错乱。
    15. 资质材料必须私有存储加时效访问凭证。营业执照、法人身份证都是敏感信息,公开可读等于任何拿到链接的人都能看。
    16. 每次查看材料都要记录操作人与时间。这类数据的访问必须可追溯,出问题时要能查「谁看过」。
    17. 下载与批量导出要更高权限并单独留档。批量导出的风险远大于单次查看。
    关键决策与取舍

    到期后选「降级」而不是「直接下线」。直接下线在合规上最干净,但它的误伤代价很高:有效期是人工录入或识别出来的,录错一位数字就会让一家正常营业的门店突然停业,而门店的损失是实打实的营业额。降级(限制接单、隐藏门店、保留登录与申诉)把误伤的代价压到可挽回的范围。判据是「两类错误的代价对比」:让过期门店多营业几小时的风险,小于错误停业一家正常门店的损失。但这个判断有边界——如果是食品安全类的强制许可,我们的处理会更严格,因为那类风险的性质不同。

    证件号冲突选「转人工」而不是「直接拒绝」。直接拒绝实现简单,但会挡住连锁品牌这类合法的一照多店场景。转人工的代价是增加了审核工作量。判据是「这个规则的例外是否合法且常见」——一照多店在连锁场景下既合法又常见,所以不能做成硬规则。这一条我总结成:唯一性约束在业务上很少是绝对的,做成「检测并提示」比做成「强制拒绝」更安全。

    驳回原因结构化的代价是运营填写麻烦了一点(要选字段、选分类)。但收益有两层:一是商家能精确知道补什么,重复提交次数明显下降二是原因可统计,能反向改进前端引导而且运营的填写成本其实降低了——原来他要用文字描述清楚是哪张有什么问题,现在选两个下拉就够了。这类「看起来增加了步骤实际减少了工作量」的改动,说服使用方的关键是让他自己体验一遍。

    踩过的坑一:完全漏掉了有效期检查这个需求。我把有效期字段存下来了,但没有任何逻辑去用它。功能测试全过(因为测试用例都是围绕用户操作设计的),上线几个月后才发现有一批门店的许可证已过期还在接单这是我在这个项目里最大的教训:我的思维停留在「用户会做什么」,没想到「时间会做什么」。后来我给自己加了一个自检项:凡是数据里有「有效期」「截止时间」「过期时间」这类字段,就必须回答「谁来检查它、检查到了做什么」——存了字段却不用,等于埋了一个几个月后才爆的雷。

    踩过的坑二:没做证件号唯一性校验,被人用一张执照开了十几家店。而且是风控同学发现的,不是我们。教训是:涉及身份、证件、资质这类数据,唯一性校验应该是默认动作,即使需求文档没写。需求文档写的是「支持上传资质」,但「资质」这个词本身就隐含了「它代表一个特定主体」的语义。

    踩过的坑三:资质图片存在公开可读的地址上。是安全同学扫出来的。我当时的想法是「链接很长猜不到」——这是典型的错误认知,因为链接会出现在日志、浏览器历史、转发的截图里。修法是改私有存储加时效凭证。教训是「难以猜到」不等于「有访问控制」。

    没做的部分:没做资质材料的自动识别(OCR 提取证件号与有效期)。它能减少人工录入错误(而录错有效期正是「直接下线」方案的主要风险来源),但需要接第三方识别服务、还要处理识别错误的兜底。我把它作为后续建议提了,并用「有效期录入错误的实际发生次数」作为依据。

    数字是怎么测的

    重复提交次数是这个模块最有说服力的指标:统计改造前后「从首次提交到审核通过的平均提交次数」。要说清样本区间和样本量,并注意剔除审核标准调整带来的影响。这个数字直接对应「驳回原因结构化」的价值。

    有效期治理的效果报「已过期但仍可接单的门店数」:改造前这个数字是未知的(没人在查),改造后应该是零或接近零报法要诚实:改造前的数字是我上线扫描任务后第一次跑出来的存量,不是「一直有监控」。

    提醒的有效性用「到期前完成续期的比例」衡量:分级提醒之后这个比例应该上升。要分渠道统计(横幅 / 短信 / 站内),这样能知道哪个渠道真正有效——我们的结论是后台横幅最有效。

    证件号冲突检测:「检出的冲突数」以及其中「经人工核查确认违规的比例」。后一个比例很重要——如果绝大多数冲突都是合法连锁,说明这个规则的提示阈值需要调整,否则只是给运营增加工作量。

    状态机的正确性用断言型用例:枚举全部非法流转,断言都被拒绝。这类用例写起来机械但很有效,而且状态机改动后能直接回归。

    访问控制的验证:断言未授权请求无法获取材料、凭证过期后失效、每次查看都产生了访问记录。第三条最容易漏测。

    不要报什么:不要报「审核效率提升 N%」——审核耗时主要取决于运营的熟练度和积压量。该报的是「平均提交次数下降」「过期仍可接单门店数归零」「到期前续期比例与分渠道效果」「冲突检出数及违规确认比例」这几件可核对的事。

    面试追问
    Q:资质审核就是上传加审核,你说的技术含量在哪? A:上传加审核那部分确实没什么技术含量,我第一版就是那么做的,功能测试全过。真正的问题是上线几个月后才暴露的——我把资质有效期字段存下来了,但没有任何逻辑去检查它,结果一批门店的许可证已经过期还在正常接单,这是合规风险。为什么功能测试发现不了?因为测试用例都是围绕用户操作设计的:提交、审核、驳回、重提,每一步都有人来触发,点一遍就覆盖了。而「有效期到了要处理」没有任何用户操作会触发它——商家不会主动来说「我的证过期了」。这类状态变化只能靠定时任务主动去扫。我漏掉它不是技术难,是思维只停留在「用户会做什么」,没想到「时间会做什么」。后来我给自己加了一条自检:凡是数据里有「有效期」「截止时间」「过期时间」这类字段,就必须回答「谁来检查它、检查到了做什么」——存了字段却不用,等于埋了一个几个月后才爆的雷。除了这个,另外两处也不是纯 CRUD:驳回原因从自由文本改成字段级结构化(原来写「材料不清晰」,商家不知道是哪张只能全套重拍,来回七八次),以及证件号唯一性校验(我漏了,结果有人用一张执照开了十几家店,是风控同学发现的)。
    Q:资质过期了为什么不直接把门店下线?留着不是有合规风险吗? A:直接下线在合规上最干净,但我判断它的误伤代价更高。关键在于有效期这个数据的来源是人工录入或识别,它本身可能是错的——录错一位数字,就会让一家正常营业的门店突然停业,而门店停业一天的营业额损失是实打实的、而且是我们造成的。所以我们的处理是降级而不是下线:限制接单、在列表里隐藏,但保留商家登录和申诉的窗口,补交审核通过后立即恢复。判据是两类错误的代价对比:让一家过期门店多营业几个小时的风险,小于错误停业一家正常门店的损失——而且前者还有其他风控手段兜着。但这个判断有边界,我想说清楚:如果是食品经营许可这类涉及公共安全的强制许可,我们的处理会更严格,因为那类风险的性质不同(不是钱的问题)。另外配套做了两件事让「降级」这个选择更站得住:一是到期前分级提醒(提前较长时间、临近、到期当天各一次),把「突然过期」的情况尽量消灭在发生前——只发一次的话商家看漏就过期了;二是提醒走商家后台顶部横幅,因为实测下来它比短信和站内消息有效得多(他每天都会登录后台处理订单)。选提醒渠道要看用户的真实行为习惯,而不是看哪个渠道听起来更正式。

模块二:平台公告定向下发

  1. 平台公告定向下发(圈选范围预览与人数确认 + 大批量任务化分批而非同步循环 + 投递幂等防重复推送 + 生效失效时间与撤回补偿)★★★
    简历这样写 平台公告下发(Spring Boot + 消息队列 + 分批任务 + 幂等去重):公告支持按城市、品类、经营状态、指定门店列表多维圈选,下发前展示命中门店数与抽样名单供运营确认,避免范围写错导致全量误发;下发从同步循环改为任务化分批投递(原实现在门店量大时接口超时且中断后无法续跑),失败条目单独重试而非整批重发;投递引入幂等键,任务重试与消息重投不会给同一门店产生重复推送;公告带生效与失效时间,由定时任务扫描到点上下架,并支持强制阅读确认(重要公告未确认时后台顶部持续提示);发错时可撤回,撤回后停止未投递部分并标记已投递部分为失效。改造后一次全量公告下发从接口超时变为后台可观测进度的稳定任务。
    展开完整拆解
    为什么要这么设计

    公告下发第一版是最简单的写法:运营在后台填标题正文、选一个范围、点发送,接口里查出符合条件的门店,循环给每家插一条站内消息测试环境几十家门店,秒级完成,看起来完全没问题。

    一是生产环境门店量大,接口直接超时。循环插入几万条,接口在网关层面就超时了。而且更糟的是——接口超时了但循环还在跑,运营不知道发到哪了,他又点了一次发送,于是前面那批门店收到了两条一样的公告。

    二是中断后无法续跑。循环跑到一半服务重启(发版),已经发了的和没发的分不清。只能全部重发一遍,于是又是重复。

    三是范围写错导致全量误发。运营想发给某个城市的餐饮门店,但品类条件没选上,结果发给了那个城市的全部门店。发出去就收不回来了——那时候还没有撤回功能。

    四是公告没有时效。一个促销活动的公告,活动结束了公告还挂在商家后台首页。而「到期下架」这件事同样没有任何用户操作会触发它——运营发完就不会再回来了。

    所以四个改动:任务化分批投递投递加幂等键下发前展示命中门店数与抽样名单加生效失效时间并由定时任务上下架,另外补了撤回

    这个模块最想说的一句话是:「循环调用」这个写法在数据量小的时候是最简单最直接的选择,但它有三个必然的问题——超时、不可续跑、重复。而这三个问题只要量级上来就一定会遇到,不存在「我们量小所以不用管」——因为量级是会涨的,而涨到出问题的那一刻,你正在处理一次线上事故。

    整体链路
    圈选范围(发错的代价很高,所以要先看清) │ ├─ 多维条件:城市 · 品类 · 经营状态 · 入驻时间 · 指定门店列表 │ 指定列表支持粘贴门店编号(运营常用,比逐个勾选快) │ ├─ 预览:命中门店数 + 抽样名单(随机取若干家展示) │ 抽样名单比数字更有用 —— 运营看到「这几家不该收到」就能发现条件错了 │ ├─ 高影响面(超过阈值)额外二次确认并回显全部筛选条件 │ 范围写错发给全量的事故就靠这一步拦住 │ └─ 圈选结果要固化成一份名单快照,不要在投递时重新查询 重新查询的问题:投递期间门店状态变化 → 实际收到的和预览的不一致 投递(任务化分批,不要同步循环) │ ├─ 提交后立即返回任务号,运营在列表里看进度 │ 同步循环的三个必然问题:接口超时 · 中断无法续跑 · 重复发送 │ ├─ 分批处理,每批记录进度游标 │ 中断后从游标续跑,不用整批重来 │ ├─ 每条投递带幂等键(公告标识 + 门店标识) │ 任务重试 · 消息重投 · 运营手滑点两次 —— 都不会重复推送 │ 这是防重复的根本手段,不能靠「运营别点两次」 │ ├─ 失败条目单独记录并可重试,不影响其他门店 │ └─ 进度与结果可查:总数 · 成功 · 失败 · 剩余 运营能看到进度,就不会因为「不知道发没发」而重复操作 生效与失效(同样没有用户操作会触发它) │ ├─ 公告带生效时间与失效时间 │ 定时生效:可以提前编排(比如活动前一天发布,当天生效) │ ├─ 定时任务扫描到点的公告,上架 / 下架 │ 不扫的后果:活动结束了公告还挂在商家后台首页 │ └─ 失效不删除数据,只是不再展示(历史要可查) 强制阅读确认(重要公告) ├─ 标记为「需确认」的公告,未确认时商家后台顶部持续提示 ├─ 记录确认人与确认时间(后续出纠纷时是依据) └─ 不要做成阻断式弹窗无法关闭 —— 会影响商家处理订单 撤回(发错之后的补救) ├─ 停止未投递部分(取消剩余批次) ├─ 已投递部分标记为失效(商家侧不再展示) ├─ 已读记录保留(不能假装没发过,出纠纷时要能查) └─ 撤回本身也是一个可观测的操作,要留档与原因 可观测 ├─ 每次下发的:命中数 · 投递成功率 · 已读率 · 确认率 ├─ 已读率异常低 → 可能是投递有问题,不是商家不看 └─ 任务积压与失败率告警
    分步拆解
    1. 圈选结果要固化成名单快照,不要在投递时重新查询。投递期间门店状态可能变化,重新查询会导致「实际收到的和预览确认的不一致」——那预览确认就失去了意义。
    2. 预览要给抽样名单而不只是数字。「命中 3421 家」运营判断不了对不对,但看到抽样里有几家明显不该收到的,他立刻就发现条件错了。抽样名单是最便宜的纠错手段。
    3. 高影响面要二次确认并回显全部筛选条件。让运营再看一眼自己选了什么。「品类没选上导致发给全城全部门店」这类事故就靠这一步拦。
    4. 下发必须任务化,不能同步循环。同步循环有三个必然问题:接口超时、中断无法续跑、运营因为不知道结果而重复操作。这三个都不是「量小就不会遇到」,而是量级涨了一定会遇到。
    5. 分批处理并记录进度游标,中断后从游标续跑。不记游标的话,服务重启后已发的和未发的分不清,只能全部重发。
    6. 每条投递必须带幂等键(公告标识 + 门店标识)。这是防重复的根本手段。任务重试、消息重投、运营手滑点两次,都会被幂等挡住——不能指望「运营别点两次」。
    7. 失败条目单独记录并可重试。整批重发会给已成功的门店再发一次(虽然有幂等挡着,但浪费时间),而且运营会担心「重发会不会重复」。
    8. 进度必须可查。这不只是体验问题——运营看不到进度就会因为「不知道发没发」而重复点击,而重复点击正是我们踩过的坑。可观测本身就是防误操作的手段。
    9. 公告要有生效与失效时间,并由定时任务上下架。「活动结束了公告要下架」这件事没有任何用户操作会触发它——运营发完就不会再回来了。和模块一的资质到期是同一类问题。
    10. 定时生效让运营可以提前编排。活动前一天准备好、当天自动生效,不用等到当天守着点发送。
    11. 失效不删除数据,只是不再展示。历史公告要可查——商家可能会问「你们当时是怎么通知的」。
    12. 重要公告的强制确认要记录确认人与时间。后续出纠纷时这是依据(「我们通知过、你确认过」)。
    13. 强制确认不要做成无法关闭的阻断式弹窗。商家后台是他处理订单的工作台,阻断会直接影响他的经营。用顶部持续提示更合适。
    14. 撤回要做两件事:停止未投递部分、把已投递部分标记失效。只做前一件的话,已经收到的商家还是会看到错误公告。
    15. 撤回后已读记录要保留,不能删。不能假装没发过——出纠纷时要能查到「确实发过、后来撤回了」。
    16. 已读率异常低要当成告警看,而不是当成「商家不看公告」。它可能说明投递本身有问题(比如某个渠道挂了)。把业务指标当成技术告警的信号,这一点很实用。
    关键决策与取舍

    圈选结果固化快照 vs 投递时实时查询。实时查询的好处是「投递时状态最新」(比如投递期间新入驻的门店也能收到)。但它有一个致命问题:实际收到的和预览确认的不一致,那预览确认就白做了——而预览确认是拦住误发的关键环节。我选固化快照,代价是投递期间的新增门店收不到这次公告判据是「预览确认的可信度比覆盖完整性更重要」,因为误发的代价(发给不该发的几万家)远大于漏发几家新店的代价(下次公告会覆盖到)。

    幂等键的粒度选「公告 + 门店」而不是「请求」。按请求做幂等只能防「同一个请求重复提交」,防不住「运营点了两次发送」(那是两个不同的请求)。按「公告 + 门店」做,无论经过多少次重试、多少个请求,同一家门店对同一条公告只会收到一次。这个粒度的选择直接决定了幂等有没有用——我最初想按请求做,幸好在设计时就发现挡不住重复点击。

    强制确认用顶部持续提示而不是阻断弹窗。阻断弹窗的「触达确定性」更高,但商家后台是他处理订单的工作台,阻断会直接影响他的经营——而且他会随手点掉确认(只为了继续工作),那个「确认」就变成了无意义的点击,反而削弱了确认记录的证明力。持续提示的触达率略低但确认是真实的。这个取舍我认为很关键:为了拿到一个「确认」而强迫用户点击,得到的确认是没有价值的。

    踩过的坑一:同步循环导致接口超时,而超时后循环还在跑,运营又点了一次发送。结果前面那批门店收到两条一样的公告。这个坑同时暴露了三个问题:同步循环撑不住量级、没有幂等、运营看不到进度所以会重复操作。我最初只想修「超时」这一个问题(想着加大超时时间),后来才明白三个问题是连着的——不可观测导致重复操作、没有幂等导致重复操作变成重复数据。教训是:批量操作的「异步化 + 幂等 + 进度可见」是一套,缺一个都不行。

    踩过的坑二:运营漏选了品类条件,公告发给了一个城市的全部门店。发出去收不回来(当时还没有撤回功能)。这件事推动了两个改动:下发前的命中数与抽样名单预览、以及撤回能力。其中抽样名单的作用超出预期——运营看到抽样里有明显不该收到的门店,立刻就发现条件错了,而单看「命中 3421 家」这个数字他判断不出对错。

    踩过的坑三:公告没有失效时间,活动结束一个月了公告还挂在商家后台首页。是商家反馈的。和模块一的资质到期是同一类问题:到点该变的状态,没有任何用户操作会触发它。教训我总结成一句:凡是内容有「时效」概念的,就必须同时设计「谁在什么时候让它失效」。

    没做的部分:没做公告的多语言与个性化变量(比如在正文里插入门店名)。个性化变量需要模板引擎和变量校验(变量名写错怎么办、取不到值怎么办),复杂度不低。而当前的公告基本都是通用内容,个性化需求主要来自「精准营销」场景,那属于另一个系统。

    数字是怎么测的

    投递能力报「一次下发的门店量级与总耗时」,并说清分批大小与并发度。只报「支持全量下发」没有意义。要报「多少家门店、分多少批、总耗时多少、期间接口无超时」。

    续跑能力是断言型用例:投递到一半强制中断(杀进程或模拟重启),断言重启后从游标继续、已投递的门店不重复收到。这条要靠打桩制造中断。

    幂等性验证要覆盖三种重复来源:同一请求重试、消息重投、运营重复点击发送。断言三种情况下同一门店都只收到一条。第三种最容易漏测但正是我们踩的坑。

    预览准确性:断言预览的命中数与实际投递的门店数一致(因为用了快照,应该完全一致)。如果不一致,说明快照机制没生效。

    生效与失效:断言到达生效时间后公告出现、到达失效时间后不再展示但数据仍可查。要用可控的时间点测,不要等真实时间到。

    撤回完整性:断言撤回后未投递部分停止、已投递部分在商家侧不再展示、已读记录仍保留。第三条容易被误删。

    已读率的报法要注意口径:分母是投递成功数还是命中数?我们用投递成功数,并且单独报投递失败数——把两件事混在一个比例里会掩盖投递问题。

    不要报什么:不要报「公告触达率 100%」——投递成功不等于商家看到了。该报的是「某量级下的下发总耗时与无超时」「中断后正确续跑且不重复」「三种重复来源都被幂等挡住」「预览数与实际投递数一致」这几件可核对的事。

    面试追问
    Q:给几万家门店发公告,循环插入不就行了吗?为什么要搞任务化? A:我第一版就是循环插入,测试环境几十家门店秒级完成,看起来完全没问题。上线后暴露的是三个连在一起的问题。一是接口超时:循环插几万条,网关层面就超时了。二是超时之后循环还在跑,运营不知道发到哪了,他又点了一次发送——前面那批门店收到了两条一样的公告。三是中断无法续跑:跑到一半服务发版重启,已发的和没发的分不清,只能全部重发,又是重复。我最初只想修「超时」这一个问题——想着把超时时间加大,后来才明白三个问题是连着的:不可观测导致运营重复操作,没有幂等导致重复操作变成重复数据。所以正确的解法是一套:任务化分批(解决超时和续跑)+ 幂等键(解决重复)+ 进度可见(解决运营重复操作),缺一个都不行。关于幂等键还有一个细节值得说:粒度必须是「公告 + 门店」而不是「请求」——按请求做幂等只能防同一个请求重复提交,防不住运营点两次发送(那是两个不同的请求)。这个粒度选错了,幂等就等于没做。我总结的是:循环调用在小数据量下是最直接的选择,但超时、不可续跑、重复这三个问题只要量级上来就必然出现,而量级是会涨的。
    Q:下发前的预览确认,为什么要给抽样名单?给个命中数量不够吗? A:因为数字判断不了对错,名单能。我们真实踩过的坑是运营想发给某个城市的餐饮门店,但品类条件没选上,结果发给了那个城市的全部门店——发出去收不回来(当时还没撤回功能)。事后复盘时我发现:如果当时给他显示「命中 3421 家」,他也判断不出这个数字是对是错(他本来就不知道该有多少家);但如果给他随机抽五家门店的名字,他一眼就能看到里面有便利店、有药店,立刻发现品类条件没生效。所以抽样名单是最便宜的纠错手段——实现成本很低,但它把「抽象的数字」变成了「他能判断的具体事实」。配套还做了两件事:一是超过阈值时二次确认并回显全部筛选条件,让他再看一眼自己选了什么;二是把圈选结果固化成名单快照,投递时不重新查询——这一点很关键,如果投递时重新查询,实际收到的就可能和预览确认的不一致,那预览确认就白做了。代价是投递期间新入驻的门店收不到这次公告,但我认为「预览确认的可信度」比「覆盖完整性」更重要:误发几万家的代价远大于漏发几家新店(下次公告会覆盖到)。

模块三:评价入库与商家回复

  1. 评价入库与商家回复(订单绑定与一单一评校验 + 评分聚合增量维护替代实时统计 + 回复的可编辑窗口 + 申诉下架后的聚合回滚)★★★
    简历这样写 门店评价与商家回复(Spring Boot + MySQL + Redis + 增量聚合):评价提交增加订单绑定校验与一单一评约束(原先仅校验登录态,存在无消费刷评与同单重复评价),并用唯一约束在数据库层兜底;门店列表与详情原先实时统计平均分与各星级数量,门店与评价量上来后成为慢查询,改为增量维护聚合值(评价新增、隐藏、删除时按差值更新,并保留定时校准兜底);商家回复设计有限可编辑窗口(发布后短时间内可改,超时锁定),兼顾笔误修正与公开内容的稳定性;商家可对疑似恶意评价发起申诉,审核通过后评价下架,聚合值同步回滚(原实现只隐藏了展示、平均分没变,商家反馈申诉没有效果)。改造后门店列表查询不再受评价量影响,评分口径可对账。
    展开完整拆解
    为什么要这么设计

    评价功能第一版做得很轻:用户提交评价存一条记录,门店详情页查这家店的全部评价算平均分。四个问题,其中两个是正确性问题、两个是性能问题。

    一是可以无消费刷评。我只校验了登录态,没有校验这个用户在这家店有没有真实的已完成订单。结果是有人批量注册账号给自己的店刷好评、给竞争对手刷差评。而且同一个订单可以反复评价——没有做一单一评的约束。

    二是平均分实时算,门店列表变慢查询。门店列表要展示每家店的评分,我在列表查询里对每家店的评价表做聚合统计。门店量和评价量都涨起来之后,这个列表接口成了整个商家侧最慢的接口。而它是用户访问量最大的接口之一。

    三是商家申诉恶意评价「没有效果」。审核通过后我把评价标记为隐藏,但门店的平均分没有变——因为平均分是实时算的,而我的隐藏逻辑漏了在统计里排除隐藏的评价。商家的感受是「申诉成功了但分数没变,等于没用」。

    四是商家回复发出去就改不了。回复是公开可见的,有商家打错字了、或者情绪化回复之后想改。完全不能改太僵化,但完全可以随时改也有问题——用户看到的回复可能和他截图时的不一样,出纠纷时说不清。

    所以四个改动:订单绑定校验加一单一评的唯一约束评分聚合改增量维护下架时聚合值同步回滚回复给一个有限的可编辑窗口

    这个模块最想说的一句话是:「统计值」和「明细」必须保持一致,而一旦你把统计值缓存或者预聚合了,「保持一致」就变成了一件需要显式设计的事。实时算的时候一致性是天然的(每次都从明细算),改成增量维护之后,任何影响明细的操作都必须同步更新聚合值——我漏掉的正是「隐藏评价」这条路径。

    整体链路
    评价提交(正确性优先) │ ├─ 校验链:登录态 → 订单存在且属于该用户 → 订单已完成 │ → 订单对应的是这家门店 → 该订单未评价过 │ 只校验登录态的后果:批量注册刷好评 / 给对手刷差评 │ ├─ 一单一评:数据库唯一约束兜底(订单标识唯一) │ 只在代码里判断挡不住并发重复提交 │ ├─ 内容检测:接敏感词检测,命中高危拦截 / 中危送审 │ └─ 入库成功后异步更新聚合值(见下) 评分聚合:从实时统计改为增量维护 │ ├─ 门店聚合表:总数 · 总分 · 各星级数量 · 有图数 · 更新时间 │ 存「总数 + 总分」而不是直接存平均分 │ 因为增量更新时只需加减,平均分由两者算出 │ ├─ 变更事件都要更新聚合(这是最容易漏的地方) │ 新增评价 → 总数 +1,总分 + 该分数 │ 隐藏 / 下架 → 总数 -1,总分 - 该分数 │ 删除 → 同隐藏 │ 修改分数 → 总分按差值调整 │ 恢复展示 → 总数 +1,总分 + 该分数 │ ├─ 更新用原子操作,避免并发丢更新 │ ├─ 定时校准兜底:定期用明细重算并比对聚合值 │ 增量维护必然会因为某些遗漏路径产生漂移 │ 有校准就不怕漂移,没校准漂移会一直累积 │ └─ 校准发现差异要告警,不能静默修正 静默修正会掩盖「哪条路径漏了更新」这个真正的问题 商家回复 │ ├─ 一条评价一条回复(不做多轮对话,避免公开互撕) │ ├─ 可编辑窗口:发布后短时间内可修改,超时锁定 │ 完全不可改 → 打错字只能挂着 │ 随时可改 → 用户看到的和他截图的不一致,出纠纷说不清 │ ├─ 每次修改留档(保留历史版本与修改时间) │ ├─ 回复同样过内容检测(商家也会写不当内容) │ └─ 可回复期限:评价发布后一段时间内可回复 过期不可回复 —— 一年前的评价突然被回复,用户会莫名收到通知 恶意评价申诉 │ ├─ 商家提交申诉:选择理由分类 + 补充证据(订单记录 · 沟通截图) │ ├─ 平台审核:通过 → 评价下架;驳回 → 保留并告知理由 │ ├─ 下架必须同步做三件事 │ 展示层隐藏 · 聚合值回滚 · 通知双方 │ 只隐藏不回滚 = 商家觉得申诉没用(我踩的坑) │ └─ 申诉有次数与频率限制(防止对所有差评都申诉) 可观测 ├─ 聚合值与明细的对账差异数(应长期为零) ├─ 申诉量与通过率(通过率过高说明审核太松) └─ 评价提交的拦截量按原因分类(订单不存在 / 重复评价 / 内容违规)
    分步拆解
    1. 评价必须绑定真实订单,校验链有五步。登录态、订单存在且属于该用户、订单已完成、订单对应这家门店、该订单未评价过。只校验登录态的后果是可以批量注册刷评——这是正确性问题不是体验问题。
    2. 一单一评要用数据库唯一约束兜底。只在代码里查一遍再插入,并发下会双写成功。唯一约束是最后一道防线。
    3. 聚合表要存「总数 + 总分」而不是直接存平均分。存总分的话增量更新只需加减;直接存平均分的话每次更新都要反推,精度会累积误差。
    4. 所有影响明细的路径都要更新聚合值,这是最容易漏的地方。新增、隐藏、下架、删除、改分数、恢复展示。我漏的是「隐藏」这一条,导致申诉通过后平均分不变。
    5. 聚合更新要用原子操作。先读再写会在并发下丢更新,而评价提交是有并发的(热门门店)。
    6. 必须有定时校准兜底:定期用明细重算并比对。增量维护必然会因为某些遗漏路径产生漂移,有校准就不怕漂移,没校准漂移会一直累积到无法收拾。
    7. 校准发现差异要告警,不能静默修正。静默修正会掩盖「哪条路径漏了更新」这个真正的问题——你只是在不断擦地,没有关水龙头。
    8. 一条评价只允许一条回复,不做多轮对话。多轮会变成公开互撕,而公开场合的争吵对门店和平台都是负面的。需要沟通应该走私信渠道。
    9. 回复给有限的可编辑窗口,不是完全不可改也不是随时可改。完全不可改太僵化(打错字只能挂着);随时可改的问题是用户看到的和他截图的不一致,出纠纷时说不清。
    10. 每次修改要留档(保留历史版本)。这是可编辑窗口能成立的前提——有留档,改动就是可追溯的。
    11. 商家回复同样要过内容检测。商家也会写不当内容,尤其是收到差评之后。只检测用户不检测商家是不对称的。
    12. 评价要有可回复期限。一年前的评价突然被回复,用户会莫名收到通知,体验很奇怪。而且这类回复通常没有实际意义。
    13. 申诉下架必须同步做三件事:展示隐藏、聚合回滚、通知双方。只做第一件的话,商家会觉得申诉没有效果——这是我踩的坑。
    14. 申诉要有次数与频率限制。否则商家会对所有差评都发起申诉,把审核资源耗尽。
    15. 提交拦截量要按原因分类统计。订单不存在、重复评价、内容违规各多少。如果「订单不存在」的拦截量突然升高,可能是有人在批量刷。
    16. 聚合值与明细的对账差异数要长期监控。这个数字应该长期为零,一旦不为零就说明有一条更新路径漏了。
    关键决策与取舍

    选增量维护聚合值而不是继续实时统计(或者加缓存)。加缓存是更简单的改法,但缓存的问题是失效策略难做——评价随时会新增,缓存要么很快过期(等于没缓存)要么数据陈旧(分数不准)。增量维护的代价是「一致性变成了需要显式设计的事」:每一条影响明细的路径都要记得更新聚合。我选增量维护,但配了定时校准兜底——因为我知道自己一定会漏某条路径(事实证明确实漏了)。判据是「读多写少且读的性能要求高」:门店列表的访问量远大于评价提交量,所以把成本转移到写侧是对的。

    聚合表存「总数 + 总分」而不是「平均分」。存平均分看起来更直观,但增量更新时要反推总分再算新平均分,浮点误差会累积;而且「各星级数量」这类需求也需要明细维度的计数。存原始的可加减量、展示时再计算,是聚合设计的通用原则。

    回复给有限可编辑窗口,是在「僵化」和「不可信」之间取的点。完全不可改:商家打错一个字就只能一直挂着,而且他会转而联系客服要求删除,成本转移到了客服。随时可改:用户截图投诉时,商家可以先改掉再说「我没说过那句话」——公开内容的可信度就没了。有限窗口加修改留档,让笔误可修正而恶意篡改可追溯。

    踩过的坑一:申诉通过后只隐藏了评价,没有回滚聚合值。商家反馈「申诉成功了但我的分数没变,等于没用」。根因是我从「实时统计」改成「增量维护」的时候,只处理了「新增评价」这一条路径,漏了「隐藏」「删除」「改分」这几条。这个坑的通用教训是:把一个「每次都重算」的逻辑改成「增量维护」,本质上是把一致性从天然的变成了需要显式保证的——所以必须先把「所有会影响这个值的路径」列成清单,逐一处理。我当时没列清单,凭印象改,就漏了。

    踩过的坑二:只校验登录态,被批量注册的账号刷评。是运营从数据异常里发现的(某家店短时间内涌入大量五星好评)。教训是:凡是「用户对某个对象的行为」都要问一句「他有资格做这件事吗」——评价的资格是「有真实的已完成订单」。需求文档写的是「用户可以评价门店」,但「用户」这个词隐含了「消费过的用户」。

    踩过的坑三:一单一评只在代码里判断,并发下被重复提交成功。用户手快点了两次提交,两个请求都查到「未评价过」,然后都插入成功了。修法是加数据库唯一约束。教训是:并发场景下「先查再写」不构成约束,唯一性必须由数据库保证。

    没做的部分:没做评价的排序与精选(把有价值的评价排前面)。它需要定义「有价值」(有图、字数、点赞数、时效性的加权),而这个权重的定义是产品决策不是技术决策,而且会直接影响商家利益(决定哪些评价被看到)。我提了方案但认为不该由我来定权重。

    数字是怎么测的

    门店列表性能改善要报清「评价量级」这个变量。因为实时统计的耗时和评价量成正比。报法是「某家评价量为 N 的门店,详情页评分部分的查询耗时从 X 降到 Y;门店列表页在返回 20 家店时总耗时从 A 降到 B」,并说明改造后的耗时与评价量基本无关——这个「无关」才是增量维护的核心收益。

    聚合准确性用对账验证,这是最重要的指标:定时任务用明细重算并与聚合值比对,报「对账差异数长期为零」。如果有差异,要能定位到是哪条路径漏了更新。这个对账机制本身就是这个模块质量的证明。

    各路径的聚合更新用断言型用例逐一覆盖:新增、隐藏、下架、删除、改分、恢复,每条路径后都断言聚合值正确。这是我踩的坑的直接回归,也是最有价值的一组用例。

    刷评拦截效果报「按原因分类的拦截量」:订单不存在、订单未完成、重复评价各拦了多少。不要只报一个总数——分类之后能看出哪种攻击方式最多。

    一单一评的并发验证:并发提交同一订单的评价,断言只有一条成功入库。要真的并发压,不是顺序调两次。

    可编辑窗口:断言窗口内可修改且留下历史版本、窗口外被拒绝。

    申诉下架的完整性:断言下架后三件事都做了——展示隐藏、聚合值回滚(平均分实际变化)、双方收到通知。第二条是坑的直接回归。

    不要报什么:不要报「刷评减少了 N%」——你不知道真实的刷评基数。该报的是「对账差异长期为零」「六条变更路径的聚合更新逐一验证」「并发下一单一评只成功一条」「列表耗时与评价量解耦」这几件可核对的事。

    面试追问
    Q:平均分为什么不直接查的时候算?加个缓存不就行了? A:实时算是我的第一版,问题是门店列表接口成了整个商家侧最慢的接口——列表要展示 20 家店的评分,就要对 20 家店的评价表各做一次聚合统计,而耗时和评价量成正比,热门门店的评价量很大。这个接口又是用户访问量最大的之一。加缓存是更简单的改法,我也考虑过,但缓存的失效策略在这里很难做:评价随时会新增,缓存要么设很短的过期时间(那等于没缓存,命中率太低)、要么设长一点(那用户刚评价完看不到分数变化,会来投诉「我评了怎么没变」)。所以我选了增量维护聚合值:聚合表存「总数 + 总分 + 各星级数量」,评价新增时加、下架时减,平均分由总数和总分算出。存总数和总分而不是直接存平均分,是因为它们是可加减的,增量更新只需加减,不会累积浮点误差。这个方案的代价我说清楚:一致性从「天然的」变成了「需要显式保证的」——实时算的时候每次都从明细算,天然一致;改成增量之后,任何影响明细的路径都必须记得同步更新聚合值我确实漏了一条(隐藏评价),导致申诉通过后平均分不变,商家反馈「申诉没效果」。所以我配了定时校准兜底:定期用明细重算并比对聚合值,差异要告警而不是静默修正——静默修正只是在擦地,没有关水龙头,你永远不知道是哪条路径漏了。
    Q:商家回复为什么要限制编辑时间?让他随便改或者干脆不让改,不是都更简单? A:两个极端我都想过,各有一个具体的问题。完全不可改:商家打错一个字、或者情绪化回复之后后悔了,回复就一直公开挂着——而他的实际反应不是接受,是去联系客服要求删除,成本转移到了客服身上,而且客服的删除操作没有留档,更不可控。随时可改:问题更严重——用户截图投诉商家的不当回复,商家可以先改掉再说「我没说过那句话」,公开内容的可信度就没了;而且用户看到的回复和他记忆里的不一致,会觉得平台在偏袒商家。所以我做的是有限可编辑窗口加修改留档:发布后短时间内可以改(覆盖笔误这个真实需求),超时锁定;每次修改保留历史版本和修改时间。这样笔误可修正、恶意篡改可追溯。这个取舍的原则我总结成:公开内容的「可修改性」要和「可追溯性」配对——允许改就必须留档,不留档的可修改等于允许篡改历史。顺带说两个配套决策:一是一条评价只允许一条回复,不做多轮对话,因为多轮会变成公开互撕,对门店和平台都是负面的,需要深入沟通应该走私信;二是评价有可回复期限,因为一年前的评价突然被回复,用户会莫名收到通知,体验很奇怪。

没有匹配的内容,换个关键词试试。

项目拆解 · 商家入驻与经营支撑(后端 · 实习级)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据