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

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

项目背景设定 本地生活的商家侧后台,Vue 3 单页应用,跑在店里的收银电脑或平板上。使用者是店员和老板,不是平台运营。核心场景是实时接单出餐菜品与估清管理配送范围与营业时间配置
为什么选这三个模块 商家后台和前面所有 Vue 后台都不一样,有两个前面没出现过的硬约束:页面要开一整天(早上开店打开,晚上关店才关,内存泄漏和连接假死这类问题在「用完就走」的后台里不会暴露,在这里必然暴露);要对接实体设备(小票打印机会缺纸、会离线、会卡纸,而漏打一单就是一次纠纷)。第三个模块的地图多边形绘制也是常规后台里少见的交互。

模块一:实时接单工作台

  1. 实时接单工作台(长连接加轮询兜底 + 持续提醒 + 小票打印)★★★
    简历这样写 商家实时接单工作台(Vue 3 + WebSocket 长连接 + 轮询兜底 + 持续声音提醒 + 云打印与本地打印双通道):新单走长连接推送为主、低频轮询兜底,未处理单持续响铃并置顶计时直到店员操作,从机制上防漏单;小票打印支持云打印机与本地打印机双通道,打印失败自动重试并保留手动补打;针对页面长时间不刷新做了内存与连接治理(订单列表定长裁剪、定时器统一注册回收、断线指数退避重连)。连续运行 12 小时后页面内存稳定在 300MB 以内无持续增长,新单从服务端推送到店内响铃约 1.5 秒,长连接假死由轮询兜底在一个轮询周期内发现。
    展开完整拆解
    为什么要这么设计

    这个页面有一个前面所有后台都没有的前提:它开一整天不刷新。早上开店打开,晚上关店才关,中间十几个小时。这个前提让一批平时不会暴露的问题必然暴露。

    一是内存持续增长最后卡死。订单一天下来几百上千条,全都堆在列表里;每个订单卡片有定时器(显示已等待时长);轮询的响应数据不断累积。到了晚上高峰期页面已经卡得点不动了,店员只能刷新页面——而刷新的瞬间可能漏掉一单。

    二是长连接假死没人发现。网络抖动后长连接在浏览器看来还是 open,但实际收不到消息。店员以为没单,其实单一直在进来,直到用户打电话来催或者平台超时罚款。这是商家最不能接受的问题——漏一单就是一次差评加一次罚款

    三是提醒响一声不够。店员在后厨忙,一声「叮」听不到。等他回到电脑前,单已经等了十分钟。

    四是打印机是实体设备,会各种坏。缺纸、离线、卡纸、被拔了电源。第一版打印失败只在控制台报了个错,店员完全不知道有单没打出来,后厨没收到小票就不做,用户等不到餐。

    所以四个设计:长连接加轮询双通道未处理单持续提醒且置顶计时打印双通道加失败重试与补打长时运行的内存与连接治理

    整体链路
    新单触达(双通道,因为漏单不可接受) │ ├─ 主通道 WebSocket 长连接 │ 服务端有新单立刻推送 │ 心跳:客户端定期发,服务端也做读空闲检测 │ 断线:指数退避重连 + 重连后拉一次全量增量补齐 │ ├─ 兜底通道 低频轮询(比如 30 秒一次) │ 作用不是「及时」,而是「发现长连接假死」 │ 轮询发现有未推送过的单 → 立刻告警并重建长连接 │ → 这一条是防漏单的关键,不能省 │ └─ 页面可见性变化时立刻拉一次 店员可能切到别的标签页或最小化了 未处理单的持续提醒(响一声不够) │ ├─ 新单进来 → 全屏提示 + 持续响铃 │ 响铃不是一次,是循环播放直到店员点「接单」或「查看」 │ 音量与铃声可配置(不同店的环境噪音差别很大) │ ├─ 未处理单在列表顶部固定,显示已等待时长并变色升级 │ 1 分钟内 正常 → 3 分钟 橙色 → 5 分钟 红色 + 加快响铃 │ ├─ 浏览器标签标题闪烁「(N) 新订单」 │ 店员切到别的标签也能从标题看到 │ └─ 声音需要用户首次交互才能播放(浏览器限制) → 进入工作台时要有一次「开启提醒音」的显式操作 并检测当前是否真的能播放,不能则醒目提示 小票打印(双通道,因为打印机会坏) │ ├─ 通道一 云打印机 │ 服务端直接把小票内容推给打印机厂商的云服务 │ 优点:不依赖店内电脑和浏览器,最可靠 │ 缺点:依赖厂商服务与网络,且要商家买指定型号 │ ├─ 通道二 本地打印机 │ 浏览器打印(需要装打印组件或用打印服务) │ 优点:零额外硬件成本,商家现有打印机就能用 │ 缺点:依赖浏览器与本机环境,故障排查困难 │ ├─ 打印结果必须回传状态(不能只发不管) │ 成功 / 失败(缺纸·离线·卡纸)/ 未知 │ ├─ 失败 → 自动重试若干次 → 仍失败则在订单上标红「未打印」 │ 并弹持续提示,让店员知道要手动补打 │ └─ 每单保留「补打」按钮(打印状态与订单状态解耦) 因为纸卡了、打歪了、被撕坏了都需要重打 长时运行的治理(页面开一整天) │ ├─ 订单列表定长裁剪:只保留今日未完成 + 最近 N 条已完成 │ 更早的走「历史订单」页面查,不留在内存 │ ├─ 定时器统一注册与回收 │ 每个订单卡片的「已等待时长」不各自开定时器 │ 改为一个全局秒级定时器统一驱动所有卡片 │ → 从 N 个定时器降为 1 个 │ ├─ 避免闭包持有已销毁的大对象(事件监听要成对解绑) │ └─ 定时自检:内存或运行时长超阈值时提示「建议刷新」 并在刷新前确保没有未处理单(避免刷新瞬间漏单)
    分步拆解
    1. 长连接必须配轮询兜底,这是防漏单的关键设计。轮询的目的不是「更及时」,而是发现长连接假死——连接在浏览器看来是 open 但实际收不到消息。轮询发现有未推送过的单,就说明长连接坏了,立刻告警并重建。只有长连接、没有兜底,假死时店员完全不知情。
    2. 重连后要拉一次增量补齐,不能只恢复连接。断线期间的单是没推过来的。重连成功后主动拉一次「上次同步位点之后的所有单」,否则那段时间的单永远收不到。
    3. 提醒必须是持续的,不能响一声。店员在后厨,一声提示音听不到。循环响铃直到他操作,并且按等待时长升级(越久越急)。铃声和音量要可配置,不同店的环境噪音差别很大。
    4. 浏览器的声音自动播放限制必须正面处理。页面没有用户交互过是不能自动播放声音的。进入工作台时要有一次显式的「开启提醒音」操作,并且要实际检测能否播放(尝试播放一个静音片段看是否被拒),不能播放时要醒目提示店员——否则会出现「以为有声音提醒,实际一直是静音」的致命情况。
    5. 标签标题闪烁是低成本高价值的补充。店员切到别的标签页时,从浏览器标签的标题就能看到「(3) 新订单」。成本极低但覆盖了一个常见场景。
    6. 打印状态必须回传并可见,不能只发不管。失败要在订单上标红「未打印」并持续提示。第一版只在控制台报错,店员完全不知道有单没打出来,后厨不做,用户等不到餐——这是最严重的一类问题。
    7. 打印状态要和订单状态解耦,且永远保留「补打」按钮。纸卡了、打歪了、小票被撕坏了都需要重打,不能因为「系统记录已打印」就不让重打
    8. 云打印和本地打印是不同商家的不同选择,两个都要支持。云打印最可靠(不依赖店内电脑)但要买指定型号;本地打印零硬件成本但依赖本机环境。让商家按自己的条件选,而不是强推一种。
    9. 订单列表必须定长裁剪。只保留今日未完成加最近若干条已完成,更早的走历史查询页。这是内存治理里收益最大的一项——一天上千条订单全留在内存里必然卡死。
    10. 把 N 个订单卡片的定时器合并成一个全局定时器。每个卡片显示「已等待 X 分钟」,如果各自开定时器,几百个订单就是几百个定时器。改成一个全局秒级定时器统一驱动所有卡片的时间显示。
    11. 要有运行时长自检并建议刷新,但刷新前必须确认没有未处理单。长时间运行难免有累积问题,主动提示刷新是务实的兜底。但刷新的瞬间可能漏单,所以必须先确认当前没有未处理的单。
    关键决策与取舍

    轮询兜底的代价是额外请求,但这个代价必须付。30 秒一次轮询,一天两千多次请求,乘以商家数量不算小。但它换来的是「长连接假死能被发现」,而漏单对商家意味着差评加罚款,对平台意味着投诉。取舍方向很明确:宁可多花点服务器资源,不可漏单。优化手段是轮询接口做得极轻(只返回「上次位点之后有没有新单」这一个判断)。

    云打印和本地打印都支持,代价是两套代码两套排障路径。但商家的条件差异是客观的——连锁品牌愿意买云打印机,夫妻小店只有一台旧的热敏打印机。强推一种会直接损失一批商家。缓解手段是把打印能力抽象成统一接口,业务层不关心走哪个通道。

    踩过的坑:长连接假死导致某商家漏了十几单。网络抖动后连接状态还是 open,服务端也认为连接活着(因为 TCP 层没断),但消息推不过去。店员一上午没看到新单,以为生意不好,直到用户打电话来问才发现。这一次事故直接推动了轮询兜底和心跳双向检测。教训是:连接状态不等于消息可达,必须用「有没有收到应该收到的消息」来判断链路是否正常。

    踩过的坑二:浏览器静音策略导致提醒音一直没响。店员早上打开页面后没有任何点击就去忙了,浏览器因为「没有用户交互」拒绝播放音频,整个上午的提醒音都是静音的,而页面上没有任何异常提示。修法是进入工作台时强制一次「开启提醒音」的交互,并且实际尝试播放一次来检测是否真的可播,不可播时在页面顶部显示醒目的红色警告。教训是:依赖浏览器策略的能力必须做「实际可用性检测」,不能假设 API 调用成功就等于功能生效。

    没做的部分:没做桌面通知(系统级弹窗)。它能在浏览器最小化时也提醒,但需要用户授权,而且商家电脑上往往还开着其他收银软件,通知会被淹没。当时靠「持续响铃 + 标签标题闪烁」覆盖,后来商家更倾向于用云打印机来兜底(打印机响了就知道有单)。

    数字是怎么测的

    12 小时内存稳定:用浏览器的性能面板做长时间录制,或者定时采样 performance.memory关键是要模拟真实的订单流入——不能空跑 12 小时,要用脚本按真实节奏(含高峰)注入订单。观察内存曲线是否呈持续上升趋势,这比绝对值更重要。要说明测试的订单总量(比如 12 小时注入 800 单),否则「300MB 以内」没有参考意义。

    推送到响铃:服务端推送时刻与客户端音频开始播放时刻各打点,约 1.5 秒。要说明这是长连接通道的数字,轮询兜底通道的延迟是一个轮询周期(最坏 30 秒),两个要分别报。

    「长连接假死能被发现」怎么验证:这是这个模块最该演练的。做法是用代理工具让 WebSocket 连接保持但丢弃消息(模拟假死),验证轮询在一个周期内发现有未推送的单、触发告警、重建连接、并补齐那段时间的单。不能只测「断线重连」——断线是显式的容易处理,假死才是真正的坑。

    打印失败的处理:物理演练——把打印机纸抽掉、拔掉网线、制造卡纸,验证每种情况下都能收到失败状态、订单标红、店员收到提示、补打可用。这类和实体设备相关的验证只能物理演练,没法用代码模拟全部情况。

    不要报「漏单率降为 0」。绝对化断言,而且漏单可能由商家自己的原因造成(电脑关机、断网)。正确表述是「因客户端链路问题导致的漏单,在双通道加持续提醒后未再出现」,并且能说出双通道的检测延迟上界。

    面试追问
    Q:一个页面开一整天不刷新,会有什么问题? A:一批平时「用完就走」的后台不会暴露的问题会必然暴露。内存持续增长:一天上千条订单全堆在列表里、每个订单卡片各自开定时器显示已等待时长、轮询响应不断累积,到晚上高峰期页面卡得点不动,店员只能刷新——而刷新瞬间可能漏单。长连接假死:网络抖动后连接在浏览器看来还是 open 但实际收不到消息。定时器与监听器泄漏:组件反复创建销毁但监听没解绑。治理手段:订单列表定长裁剪(只留今日未完成加最近若干已完成,更早的走历史页查,这项收益最大);把 N 个卡片的定时器合并成一个全局秒级定时器统一驱动所有时间显示;事件监听成对解绑定时自检运行时长和内存,超阈值提示刷新——但刷新前必须确认没有未处理单,否则刷新瞬间会漏。
    Q:怎么保证商家绝对不漏单? A:核心是双通道 + 持续提醒,因为单一通道一定会有失效的时候。主通道是 WebSocket 长连接(及时);兜底是低频轮询(30 秒一次),轮询的目的不是更及时,而是发现长连接假死——轮询如果发现有未推送过的单,说明长连接坏了,立刻告警并重建连接。我们真踩过这个坑:连接状态是 open、TCP 层也没断,但消息推不过去,店员一上午没看到新单以为生意不好,直到用户打电话来问教训是连接状态不等于消息可达,必须用「有没有收到应该收到的消息」来判断链路正常。除此之外:重连后要主动拉增量补齐断线期间的单;页面可见性变化时立刻拉一次未处理单持续响铃并按等待时长升级(响一声店员在后厨听不到);标签标题闪烁「(N) 新订单」覆盖切标签的场景。
    Q:打印机缺纸了,你怎么知道,店员怎么知道? A:关键是打印结果必须回传状态并且在界面上可见,不能只发不管。第一版就是只发打印指令,失败只在控制台报错,店员完全不知道有单没打出来,后厨不做,用户等不到餐——这是最严重的一类问题。正确做法:打印结果分成功 / 失败(区分缺纸、离线、卡纸)/ 未知三态回传;失败先自动重试若干次;仍失败则在订单卡片上标红「未打印」并弹持续提示每单永远保留「补打」按钮——因为纸卡了、打歪了、小票被撕坏了都需要重打,不能因为系统记录「已打印」就不让重打,打印状态要和订单状态解耦。另外我们支持云打印和本地打印双通道:云打印不依赖店内电脑最可靠(打印机自己响了店员就知道有单),但要买指定型号;本地打印零硬件成本但依赖本机环境。商家条件差异是客观的,强推一种会损失一批商家。
    Q:浏览器不让自动播放声音,提醒音怎么办? A:这是我们踩过的一个很隐蔽的坑:店员早上打开页面后没点击任何东西就去忙了,浏览器因为「没有用户交互」拒绝播放音频,整个上午提醒音都是静音的,而页面上没有任何异常提示。修法两步:进入工作台时强制一次显式的「开启提醒音」交互(这次点击就满足了浏览器的用户交互要求);更重要的是实际尝试播放一次来检测是否真的可播(比如播一个极短的静音片段,看 Promise 是否被 reject),不可播时在页面顶部显示醒目的红色警告并阻塞使用。通用教训是:依赖浏览器策略的能力必须做「实际可用性检测」,不能假设 API 调用没报错就等于功能生效。同类的还有通知权限、剪贴板权限、全屏 API——都要检测实际效果而不是只看调用是否抛异常。

模块二:菜品与多门店批量管理

  1. 菜品与多门店批量管理(规格组合 + 估清即时生效 + 差异化批量)★★★
    简历这样写 菜品与多门店批量管理(Vue 3 + 规格组合矩阵 + 估清即时生效 + 多门店差异化批量下发):把「规格 + 做法」的组合做成矩阵式编辑(自动生成组合、只对差异项单独设价、支持批量禁用不可售组合);估清(售罄)操作走独立轻接口并即时生效到用户端,与菜品编辑解耦;连锁商家的批量改价支持先看差异再选择性下发(哪些门店当前值不同、下发后变成什么),避免一刀切覆盖门店的本地特价。3 规格 × 4 做法共 12 个组合的编辑由逐个填写变为矩阵批量,估清从操作到用户端不可见约 2 秒,多门店批量下发前可逐店预览差异。
    展开完整拆解
    为什么要这么设计

    菜品管理看起来是最普通的增删改查,但真做起来有三个问题让第一版几乎不可用。

    一是规格组合爆炸。一个奶茶有「大杯中杯小杯」三个规格、「常温少冰去冰全冰」四个做法,组合起来 12 个 SKU,每个可能有不同价格。第一版是让商家逐个添加 SKU,填 12 次表单,而且改价要再改 12 次。商家的做法是干脆不用规格,把「大杯」「中杯」建成两个独立菜品——数据就乱了

    二是估清(售罄)操作太慢,而它是最高频的操作。某个菜卖完了,店员要点进菜品编辑页、改库存或改状态、保存、等刷新。而估清在饭点是每几分钟就要做一次的操作,而且要求立刻生效——晚一分钟就可能有用户下了一单卖不了的菜,最后是退单加差评。第一版把估清做成了「编辑菜品的一个字段」,路径长且和其他字段一起保存,慢且容易误改。

    三是连锁商家的批量改价把门店特价冲掉了。总部要给所有门店涨价,一键下发,结果把某几家店针对周边竞争做的本地特价全覆盖了,门店店长来投诉。第一版的批量下发是无脑覆盖,没有任何差异提示。

    所以三个设计:规格组合做成矩阵编辑估清独立成轻操作并即时生效批量下发前展示逐店差异并允许选择性下发

    整体链路
    规格组合矩阵(不要让商家逐个填 SKU) │ ├─ 第一步 定义规格维度 │ 规格组一:杯型 大杯 / 中杯 / 小杯 │ 规格组二:做法 常温 / 少冰 / 去冰 / 全冰 │ ├─ 第二步 系统自动生成组合矩阵(3 × 4 = 12 个) │ 以表格呈现:行是杯型,列是做法 │ 每格是一个可售组合,格内可填价格与库存 │ ├─ 第三步 批量填充(这是省力的关键) │ 整行填充:所有大杯统一加价 3 元 │ 整列填充:所有去冰不加价 │ 全表填充:先统一设一个基准价,再改差异项 │ → 实际只需要改少数几个差异格 │ ├─ 第四步 禁用不可售组合 │ 比如「小杯 + 全冰」不做 → 该格标为不可售 │ 不可售的组合不生成 SKU,用户端也选不到 │ └─ 提交:只提交有值或状态变更的格子(差异提交) 估清(独立成轻操作,因为它最高频且要求即时) │ ├─ 入口做在菜品列表上,一键切换,不进编辑页 │ 列表上每个菜品一个「估清」开关 │ 支持「今日估清」(次日自动恢复)与「长期下架」 │ ├─ 走独立的轻接口,只改可售状态这一个字段 │ 不走菜品编辑的完整保存(避免误改其他字段) │ → 接口极轻,响应快 │ ├─ 服务端改状态后主动失效用户端的菜单缓存 │ 并向已打开该店菜单的用户端推送变更 │ → 目标是「估清后用户端立刻看不到」 │ ├─ 乐观更新界面 + 失败回滚 │ 店员点了立刻变灰,失败则弹提示并恢复 │ └─ 估清操作留痕(谁在什么时候估清了什么) 用于纠纷:用户说下单时还有货 连锁商家的批量下发(先看差异,再选择性下发) │ ├─ 总部编辑「标准菜品」→ 选择要下发的门店 │ ├─ 下发前生成差异预览(关键的一步) │ 逐门店列出:该门店当前值 / 下发后的值 / 是否会被改变 │ 标出「该门店有本地自定义价」的项并高亮警示 │ ├─ 允许选择性下发 │ 全部覆盖 / 只下发给「与标准值一致」的门店 │ / 逐店勾选 / 排除有本地特价的门店 │ → 默认选项是「排除有本地自定义的门店」(最安全) │ ├─ 下发生成变更批次,可整批回滚 │ 逐门店逐字段记录改前值(这是回滚的唯一依据) │ └─ 门店侧可见「本项由总部下发」标记 并可申请恢复本地自定义(走审批)
    分步拆解
    1. 规格先定义维度,再由系统生成组合,绝不让商家逐个建 SKU。让商家填 12 次表单的结果是他放弃使用规格功能、把不同规格建成独立菜品,数据结构直接崩坏。矩阵编辑让他只需要定义两个维度然后改少数差异格。
    2. 矩阵要支持整行整列和全表填充。真实的定价规律通常是「按某个维度加价」——所有大杯加 3 元、所有去冰不加价。整行整列填充直接对应了这个规律,商家改几下就完事。
    3. 要能禁用不可售的组合。不是所有组合都存在(小杯不做全冰)。禁用的组合不生成 SKU、用户端也选不到,否则用户下单了商家做不出来。
    4. 估清必须做成列表上的一键操作,不能藏在编辑页里。它是饭点每几分钟就要做的最高频操作。路径长一步,店员就会嫌麻烦而不及时估清,后果是用户下了卖不了的单。
    5. 估清走独立的轻接口,只改可售状态一个字段。不走菜品编辑的完整保存——一是快,二是避免误改其他字段(完整保存会把编辑页里的所有字段都提交一遍,如果店员之前误改过什么,这次估清就会把它一起提交)。
    6. 估清要区分「今日估清」和「长期下架」。今日估清次日自动恢复(今天的食材用完了,明天还卖),长期下架要手动恢复(这个菜不做了)。混成一个会导致店员每天早上要手动恢复一堆菜。
    7. 估清后要主动失效用户端缓存并推送变更。目标是「估清后用户端立刻看不到」。光改数据库不够——用户端可能有菜单缓存,也可能已经打开着菜单页。
    8. 估清操作要留痕。用户投诉「我下单时明明有货」,需要能查出估清的确切时刻来判断是谁的问题。
    9. 多门店批量下发前必须展示逐店差异,这是防覆盖本地特价的关键。逐门店列出「当前值 / 下发后的值 / 是否会变」,并高亮标出有本地自定义价的门店
    10. 批量下发的默认选项应该是最安全的那个。默认「排除有本地自定义的门店」,想全覆盖要显式选。默认值的选择就是风险防线——总部的人往往一路点确定。
    11. 下发要生成变更批次并逐门店逐字段记录改前值。这是回滚的唯一依据。只记「下发了什么」无法回滚,因为各门店的原值各不相同。
    关键决策与取舍

    矩阵编辑在维度多时会退化。两个维度是表格,三个维度(杯型 × 做法 × 糖度)就是三维,没法用平面表格表达。我们的处理是限制规格组数量(最多两组进矩阵,第三组以上退回列表式编辑),并且在界面上说明原因。承认「这个交互有适用范围」比硬做一个三维编辑器好——后者的可用性一定很差。

    估清即时生效的代价是缓存失效的复杂度。菜单缓存在多层(CDN、服务端缓存、客户端缓存),估清要让每一层都及时失效。做法是菜单缓存的 key 带一个店铺级的版本号,估清时递增版本号,所有层的旧缓存自然失效。代价是估清会导致整个店的菜单缓存全部失效(而不只是那一个菜),高频估清时会有一波回源。这个代价可以接受——估清的频率虽然高,但相对于菜单查询量还是低得多。

    踩过的坑:批量下发覆盖了门店本地特价,门店店长集体投诉。总部统一涨价,一键下发到 40 家店,其中 6 家店针对周边竞争做的本地特价被全部覆盖,这几家店的销量当天就掉了,店长投诉到总部。修法就是差异预览加选择性下发,并且把默认选项改成「排除有本地自定义的门店」教训是:批量操作的默认行为必须是最保守的那个,因为操作者往往一路点确定。

    踩过的坑二:估清走了菜品完整保存接口,把店员之前的误改一起提交了。店员在编辑页误改了菜品描述但没保存就离开了(表单有本地暂存),后来他在列表上点估清,而估清复用的是完整保存接口,把暂存的错误描述一起提交上去了。修法是估清用独立的轻接口,只改可售状态这一个字段。教训是:高频的单字段操作必须有专用接口,复用完整保存接口会带来意料之外的字段变更。

    没做的部分:没做菜品的定时上下架(比如午市菜品到点自动上架)。商家提过,需要定时任务和时区处理,而且和「今日估清次日恢复」的逻辑有交叉,当时怕把状态模型搞复杂,没做。

    数字是怎么测的

    规格组合的编辑成本:「12 个组合由逐个填写变为矩阵批量,实际需要改动的格子数」这个对比比「效率提升 N 倍」诚实——实际改动格子数取决于定价规律(如果 12 个价格全不同,矩阵也帮不上忙;而真实情况通常是按维度加价,只需改 2 到 3 处)。要说明这个前提。

    估清生效延迟:从店员点击到用户端菜单不再展示该菜品,约 2 秒。要说清链路(改状态、递增菜单版本号、各层缓存失效、用户端推送或下次请求),以及「已打开菜单页的用户」和「新进入的用户」两种情况分别是多久——前者靠推送,后者靠缓存版本号,延迟不同。

    批量下发的差异预览:「差异预览拦下多少次会覆盖本地特价的下发」。这是个绝对数、口径清晰,而且直接证明这个功能的价值。比说「我做了差异预览」有说服力。

    不要报「菜品管理效率提升」或「估清准确率」。前者算不出基线;后者「准确」没有明确定义。可以报的是「因未及时估清导致的退单量」变化——但要注意这个指标受商家自身勤快程度影响,我们只能说降低了操作成本。

    面试追问
    Q:一个菜品有 3 个规格 4 种做法,12 个 SKU,怎么让商家不用填 12 次? A:先定义维度,由系统生成组合矩阵,再批量填充。商家只定义两个规格组(杯型三项、做法四项),系统自动生成 3×4 的表格,行是杯型列是做法,每格是一个可售组合。关键是支持整行整列和全表填充——因为真实的定价规律通常是「按某个维度加价」(所有大杯加 3 元、所有去冰不加价),整行整列填充直接对应这个规律,商家实际只需要改少数几个差异格。还要能禁用不可售组合(小杯不做全冰,该格标为不可售、不生成 SKU、用户端也选不到)。为什么必须这么做:第一版让商家逐个建 SKU,他填了两次就放弃了,改成把「大杯」「中杯」建成两个独立菜品,数据结构直接崩坏——这说明交互成本过高会导致用户绕过功能,而绕过的方式往往破坏数据模型。矩阵的局限要说明:两个维度是表格,三个维度以上没法用平面表格表达,所以我限制最多两组进矩阵,更多的退回列表式编辑。
    Q:菜卖完了,怎么让用户端立刻看不到? A:两件事,操作路径要短,生效链路要全路径:估清做成菜品列表上的一键开关,不进编辑页——它是饭点每几分钟就要做的最高频操作,路径长一步店员就会嫌麻烦不及时估清,后果是用户下了卖不了的单然后退单加差评。而且要走独立的轻接口只改可售状态一个字段,不复用菜品完整保存(我们踩过坑:店员在编辑页误改了描述有本地暂存,后来点估清时复用完整保存接口,把错误描述一起提交了)。生效链路:菜单缓存在多层(CDN、服务端、客户端),做法是菜单缓存 key 带店铺级版本号,估清时递增版本号,所有层的旧缓存自然失效;同时向已打开该店菜单的用户端推送变更代价是整个店的菜单缓存会全部失效(不只那一个菜),会有一波回源,但估清频率相对菜单查询量还是低得多,可以接受。另外估清要区分「今日估清」(次日自动恢复)和「长期下架」,混成一个会让店员每天早上手动恢复一堆菜。
    Q:连锁总部要给 40 家店统一涨价,怎么不覆盖某几家店的本地特价? A:下发前必须展示逐店差异,并允许选择性下发。我们踩过这个坑:总部一键下发,其中 6 家针对周边竞争做的本地特价被全覆盖,这几家店当天销量就掉了,店长投诉到总部。修法是下发前生成差异预览——逐门店列出「该店当前值 / 下发后的值 / 是否会被改变」,并高亮标出有本地自定义价的门店;然后提供多种下发方式(全部覆盖 / 只下发给与标准值一致的门店 / 逐店勾选 / 排除有本地自定义的),默认选项是「排除有本地自定义的门店」默认值的选择本身就是风险防线——总部的人往往一路点确定。另外下发要生成变更批次并逐门店逐字段记录改前值(这是回滚的唯一依据,只记「下发了什么」无法回滚,因为各门店原值不同),门店侧还要能看到「本项由总部下发」的标记并可申请恢复本地自定义。
    Q:估清操作用了乐观更新,如果实际失败了怎么办? A:乐观更新 + 失败回滚 + 明确提示。店员点估清后界面立刻变灰(因为他要马上去忙,不能等),请求失败则恢复原状态并弹醒目提示「估清失败,该菜品仍在售,请重试」。这里乐观更新是合适的,因为估清是「商家对自己菜品状态的单方面声明」,服务端几乎必然接受,失败只可能是网络问题——符合我在骑手端总结的判断标准(失败概率低、猜错代价低)。但失败提示必须醒目且不能自动消失,因为后果很具体:店员以为估清了,用户还在下单,最后是退单纠纷。另外要留痕:谁在什么时候估清了什么,用户投诉「我下单时明明有货」时能查出估清的确切时刻来判断责任。这个留痕在纠纷处理里比在技术上更有价值。

模块三:配送范围与营业时间配置

  1. 配送范围与营业时间配置(地图多边形绘制 + 时段模型 + 生效校验)★★★
    简历这样写 配送范围与营业时间配置(Vue 3 + 地图多边形绘制编辑 + 多时段模型 + 保存前校验):配送范围支持在地图上绘制与编辑多边形(拖拽顶点、插入删除顶点、多个区域各自设起送价与配送费),保存前做自相交、面积上限、是否包含门店等几何校验;营业时间支持一天多时段与按周差异化,并叠加节假日与临时休息的例外规则;配置生效前给出影响预览(覆盖范围变化、当前时段是否营业)。多边形编辑的自相交与门店不在范围内等错误在保存前被拦下,营业时间的跨零点时段与例外规则由统一模型表达。
    展开完整拆解
    为什么要这么设计

    这两个配置看起来是小功能,但它们直接决定商家能不能接到单,配错了商家会认为是平台的问题。

    第一版的配送范围是「填一个半径」,营业时间是「填开始和结束时间」。都不够用。

    一是圆形范围完全不符合实际。门店旁边隔着一条河或者一条高速,直线距离一公里但实际要绕十公里;反过来沿着主干道三公里内很好送。用半径的结果是「近的送不了、远的送不到」,商家只能把半径调小,损失了本该能做的生意。

    二是一天只有一个营业时段不够。很多店是「午市 11 到 14 点、晚市 17 到 21 点」,中间休息。第一版只能填一个时段,商家只好填 11 到 21 点,结果下午三点有人下单,店里没人做,只能拒单,而拒单会影响商家评分。

    三是跨零点的时段表达不了。夜宵店是 18 点到次日 2 点。第一版的「开始时间小于结束时间」校验直接把这种情况判为非法。

    四是配错了没有任何提示。商家把多边形画歪了、门店压根不在自己画的范围里、或者把范围画成了自相交的形状,系统照样保存,然后就没单了,商家来投诉「你们不给我派单」。

    所以四个设计:范围用多边形而不是半径营业时间支持一天多时段与按周差异时段模型要能表达跨零点保存前做几何与逻辑校验并给影响预览

    整体链路
    配送范围(多边形绘制与编辑) │ ├─ 地图上绘制:点击落点,闭合成多边形 │ 顶点可拖拽移动、可在边上插入新顶点、可删除顶点 │ 顶点数有上限(太多会让服务端的点在多边形内判定变慢) │ ├─ 支持多个区域,各自独立设置 │ 区域一(近距离)起送价低、配送费低 │ 区域二(远距离)起送价高、配送费高 │ → 用嵌套或并列的多个多边形表达阶梯配送费 │ ├─ 保存前的几何校验(这是防配错的关键) │ 1 多边形不能自相交(自相交的「内外」判定是未定义的) │ 2 顶点数不少于 3 且不多于上限 │ 3 门店坐标必须落在范围内(否则商家自己都不在配送区) │ 4 面积不超过上限(防止画一个覆盖全城的范围) │ 5 多个区域之间的重叠要提示(重叠处按哪个区域的费率) │ ├─ 影响预览 │ 与上一版范围对比:新增覆盖了哪些区域、失去了哪些 │ 估算范围内的住宅与写字楼密度(如果有数据) │ └─ 服务端存储与判定 存多边形顶点序列 下单时判定用户地址是否在范围内(点在多边形内算法) 为性能先用外接矩形快速排除,再做精确判定 营业时间(一天多时段 + 按周差异 + 例外规则) │ ├─ 基础模型:按周几分别配置若干时段 │ 周一到周五:11:00-14:00, 17:00-21:00 │ 周六周日: 10:00-22:00 │ ├─ 跨零点时段的表达 │ 18:00-02:00 存成「起 18:00,止 02:00,跨天标记为真」 │ 判定「当前是否营业」时要把跨天时段拆到两天上算 │ → 不要用「开始必须小于结束」的校验,那会禁掉夜宵店 │ ├─ 例外规则(叠加在基础模型之上,优先级更高) │ 节假日特殊营业时间(春节只营业半天) │ 临时休息(今天设备坏了,立刻停止接单) │ → 临时休息要能一键开启且立刻生效 │ ├─ 保存前校验 │ 同一天的时段不能重叠 │ 时段时长不能为零 │ 至少要有一天是营业的(全周不营业要二次确认) │ └─ 影响预览 「按此配置,当前时刻状态为:营业中 / 休息中」 → 让商家立刻看到配置的实际效果,而不是保存后才发现 判定「当前能否下单」的完整链路(服务端) ├─ 1 商家是否在营业时段内(含例外规则) ├─ 2 商家是否处于临时休息 ├─ 3 用户地址是否在配送范围内 ├─ 4 是否满足该区域的起送价 └─ 任一不满足 → 用户端给出明确原因,而不是笼统的「不可下单」
    分步拆解
    1. 配送范围必须用多边形,半径无法表达真实的可送区域。河流、高速、铁路会让直线距离和实际距离严重偏离。多边形让商家自己画出「我能送到哪」,这是他最了解的事。
    2. 多边形编辑要支持顶点的增删改,不能只支持重画。商家微调范围时不应该重画整个多边形。拖拽顶点、在边上插入顶点、删除顶点这三个操作是必需的。
    3. 顶点数要有上限。顶点越多,服务端做「点在多边形内」判定的开销越大,而这个判定在每次下单时都要跑。限制顶点数是性能和表达力的权衡,几十个顶点足够表达实际的配送区。
    4. 多区域是表达阶梯配送费的方式。近距离区起送价和配送费低,远距离区高。用多个多边形表达比用「按距离分段」更准确,因为实际的远近不等于直线距离。
    5. 自相交校验是必须的,因为自相交多边形的「内外」是未定义的。商家画的时候手一抖就可能形成自相交,而不同的点在多边形内算法对自相交图形会给出不同结果,导致「有时判定在范围内有时不在」。必须在保存前拦下。
    6. 门店必须在自己画的范围内,这个校验能拦掉一类低级但常见的错误。商家画偏了,自己店都不在范围里。这种配置几乎接不到单,而商家会认为是平台不派单。
    7. 营业时间要支持一天多时段,这是最基础的需求。午市晚市中间休息是普遍情况。只支持一个时段会导致商家填一个大区间然后靠拒单来应付,而拒单影响他的评分。
    8. 跨零点的时段必须能表达,不要用「开始小于结束」的校验。夜宵店 18 点到次日 2 点是正常业务。存储上加「跨天」标记,判定时把跨天时段拆到两天上算。
    9. 例外规则要能叠加并且优先级更高。节假日特殊营业时间、临时休息,这些是覆盖基础配置的。临时休息尤其要能一键开启且立刻生效——设备坏了要马上停止接单,慢一分钟就多一个做不出来的单。
    10. 保存前给「当前时刻的实际状态」预览。「按此配置,现在是营业中」——让商家立刻验证配置对不对,而不是保存后等着看有没有单进来。这个预览成本极低但能挡掉大部分配置错误。
    11. 服务端的「不可下单」原因要具体。是不在营业时间、还是超出配送范围、还是没到起送价,三个原因对用户的意义完全不同(第三个他可以多买点凑单)。笼统的「不可下单」会直接损失订单。
    关键决策与取舍

    多边形比半径灵活,代价是商家要会画、我们要做校验。部分商家(尤其是年纪大的店主)不会用地图绘制。缓解手段是保留「按半径快速生成」作为起点——先生成一个圆形近似的多边形,商家再拖顶点微调。这比「二选一」好:既降低了上手门槛,又保留了精细调整的能力。

    点在多边形内的判定要做性能优化,因为它在下单链路上。每次下单都要判定用户地址是否在范围内,而多边形可能有几十个顶点。做法是先用外接矩形快速排除(绝大多数不在范围的地址会在这一步被排除,成本是几次数值比较),只有通过矩形判定的才做精确的多边形判定。这是典型的「便宜的粗筛 + 昂贵的精判」两段结构,和搜索里的网格预筛是同一个思路。

    踩过的坑:自相交多边形导致「同一个地址有时能下单有时不能」。商家画的范围自相交了,我们的判定用的是射线法,对自相交图形,射线法的结果取决于射线方向,而不同请求的计算路径略有差异,结果同一个地址的判定不稳定。用户投诉「刚才还能下单现在不能了」,我们查了很久才定位到是多边形本身非法。修法是保存时做自相交校验并拒绝保存教训是:几何数据必须在入口做合法性校验,因为下游算法对非法输入的行为是未定义的。

    踩过的坑二:跨零点时段被校验规则禁掉,夜宵商家没法配。第一版有个「结束时间必须晚于开始时间」的校验,看起来合理,但把所有夜宵店都挡住了,商家只能配成「18:00-23:59」然后次日的部分完全接不到单。修法是加「跨天」标记,判定时把跨天时段拆开算。教训是:看起来「显然合理」的校验规则往往隐含了对业务的错误假设,加校验前要想清楚有没有正当的反例。

    没做的部分:没做基于实际路网的可达范围生成(输入配送时长上限,自动生成「15 分钟骑行可达」的等时圈)。这比手画准确得多,但需要路网数据和等时圈计算服务,成本较高,当时是让商家自己画。

    数字是怎么测的

    「配置错误在保存前被拦下」怎么量化:各类校验的触发次数分布(自相交、门店不在范围内、时段重叠、面积超限)。这个分布很有信息量——如果「门店不在范围内」占大头,说明绘制交互有问题(可能是地图初始位置没定位到门店),要改交互而不只是加校验。

    点在多边形内判定的性能:压测下单接口,对比「有外接矩形预筛」和「直接精确判定」的耗时差。要说明多边形的顶点数,因为耗时和顶点数直接相关。

    「跨零点时段正确」怎么验证:写单测覆盖边界——18:00 到次日 02:00 的配置下,判定 19:00、23:59、00:01、01:59、03:00 各时刻的营业状态。这类逻辑用单测覆盖边界比用线上数据说明更有力,而且要覆盖跨月、跨年、夏令时切换(如果业务涉及有夏令时的地区)。

    影响预览的价值:可以报「保存前预览显示与商家预期不符、商家因此修改了配置的次数」——如果有埋点的话。这个数字直接证明预览拦下了错误配置。

    不要报「商家配置错误率下降 N%」。没有可靠基线(改造前的错误配置压根没被记录,因为没有校验)。正确表述是「哪些错误现在会被拦下、各拦下多少次」。

    面试追问
    Q:配送范围为什么不用「以门店为中心 N 公里」这种半径? A:因为直线距离和实际可达性经常严重偏离。门店旁边隔着一条河、一条高速或者铁路,直线一公里但实际要绕十公里;反过来沿着主干道三公里内很好送。用半径的结果是「近的送不了、远的送不到」,商家为了避免送不到的单只能把半径调小,损失了本该能做的生意。多边形让商家自己画出「我能送到哪」——这是他最了解的事。而且多边形还能表达阶梯配送费:用多个多边形表示近距离区和远距离区,各自设不同的起送价和配送费,比「按直线距离分段」准确得多。代价是部分商家(尤其年纪大的店主)不会用地图绘制,缓解手段是保留「按半径快速生成一个圆形近似多边形」作为起点,商家再拖顶点微调——既降低上手门槛,又保留精细调整能力,比「二选一」好。
    Q:商家画的多边形自相交了会怎样? A:「内外」判定变成未定义的,导致同一个地址有时能下单有时不能。我们真踩过:判定用的是射线法,对自相交图形射线法的结果取决于射线方向,不同请求的计算路径略有差异,结果判定不稳定。用户投诉「刚才还能下单现在不能了」,查了很久才定位到是多边形本身非法。修法是保存时做自相交校验并直接拒绝保存,同时在界面上把相交的边高亮出来让商家知道改哪里。教训是几何数据必须在入口做合法性校验,因为下游算法对非法输入的行为是未定义的。相关的其他校验也要一起做:顶点数不少于 3、不多于上限(顶点越多下单时的判定越慢,而这个判定在下单链路上)、门店坐标必须落在范围内(商家画偏了自己店都不在范围里,这种配置几乎接不到单,而他会认为是平台不派单)、面积不超上限(防止画一个覆盖全城的范围)。
    Q:夜宵店营业到凌晨 2 点,这个时段怎么存怎么判断? A:存储上加「跨天」标记,判定时把跨天时段拆到两天上算。18:00 到次日 02:00 存成「起 18:00,止 02:00,跨天为真」。判定「当前是否营业」时,如果是跨天时段,要检查「当前时间晚于 18:00」或者「当前时间早于 02:00 且前一天配置了这个跨天时段」。关键是不要加「结束时间必须晚于开始时间」这种校验——我们第一版加了,看起来显然合理,但把所有夜宵店都挡住了,商家只能配成 18:00-23:59,次日的部分完全接不到单。教训是:看起来「显然合理」的校验规则往往隐含了对业务的错误假设,加校验前要想清楚有没有正当的反例。另外这类时间逻辑必须用单测覆盖边界:19:00、23:59、00:01、01:59、03:00 各时刻的判定结果,以及跨月跨年、涉及夏令时的地区。
    Q:每次下单都要判断地址是否在配送范围内,性能怎么保证? A:用「便宜的粗筛 + 昂贵的精判」两段结构。第一段先算多边形的外接矩形,判断地址坐标是否在矩形内——这只需要几次数值比较,成本极低,而绝大多数不在范围内的地址会在这一步被排除。只有通过矩形判定的才做精确的「点在多边形内」判定(射线法或环绕数法),这一步的成本和顶点数相关。这和搜索里的「GeoHash 网格预筛 + 精确距离过滤」是完全同一个思路——用一个廉价的近似判定把候选集缩小,再对小候选集做精确计算。配套还要限制顶点数上限,因为顶点越多精判越慢,而这个判定在下单链路上、每单都要跑。外接矩形可以在保存多边形时就算好存起来,不用每次现算。

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

项目拆解 · 商家管理后台(Vue)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据