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

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

实习级这一档是干什么的 骑手端的轨迹上报与路径规划、实时运力调度这些核心台面,实习生大概率碰不到。这一档收的是商家经营端里实习生真的会被分派、但仍然有明确技术含量的活。下面三个模块彼此独立——只做过一个就只写一个。
怎么讲才不显得 low 「我对接了个蓝牙打印机,调 API 写数据」和「低功耗蓝牙单次写入有长度上限,一张小票几百字节必须分包串行写、包间还要留间隔,否则设备丢数据打出乱码;而且小票有中文,要先转成设备支持的编码,小程序里没有现成的编码能力」——同一件事,后者面试官会顺着追问。这一档的破解办法是认清「这是商家的生产工具」:打印失败顾客拿不到小票、漏一单顾客白等、估清不同步顾客点了没有的菜,每一个失败都直接影响他做生意,所以兜底和失败反馈是硬要求。
三条自检 一、能说出不这么做会怎样(不分包会打出乱码、不做未接单持续提醒会漏单、估清不同步会超卖);二、能说出你踩过的具体坑;三、能说出量级(小票多少字节分多少包、高峰期每分钟多少单、店里几台设备同时操作)。三条都有就能写。
项目背景设定 本地生活平台的商家经营端(uni-app,主要跑在微信小程序与安卓 App),Vue 3 组合式 API。使用者是店里的老板、店长和后厨,设备是店里的安卓平板或老板的手机,外接一台低功耗蓝牙小票打印机。
为什么这三块值得写 它们的共同点是这是商家的生产工具,失败会直接造成经营损失:小票打不出来后厨不知道做什么、漏一单顾客白等半小时、估清没同步顾客点了没有的菜要退单。所以这三块的技术含量不在功能实现,在失败路径的处理——分包丢数据怎么办、页面被切到后台怎么办、两台设备同时操作怎么办。这些在开发机上单人操作时全都不出现。

模块一:蓝牙小票打印

  1. 蓝牙小票打印(指令拼装与中文编码转换 + 长度上限下的分包串行写入 + 断连重连与打印队列 + 失败可补打不重复)★★★
    简历这样写 蓝牙小票打印(uni-app + 低功耗蓝牙 + ESC/POS 指令 + 编码转换):小票内容按打印指令集拼装(对齐、字号、加粗、分割线、走纸切纸),中文部分做编码转换后再转字节(小程序环境无原生转换能力,改为内置精简码表按需查表);低功耗蓝牙单次写入有长度上限,一张小票数百字节,改为按上限分包并串行写入、包间加间隔,解决原先一次性写入导致设备丢数据打出乱码的问题;实现打印队列(高峰期多单同时到达时排队串行处理,避免并发写入冲突)与断连自动重连;打印结果逐单记录状态并支持补打,补打带幂等标记不会重复出票;设备选择、连接状态与最近打印结果在界面上常驻可见,商家能立刻发现打印机离线。上线后因打印失败导致的漏做单明显减少。
    展开完整拆解
    为什么要这么设计

    接蓝牙打印机看起来就是「连上设备、把小票内容写进去」。我按文档调通了「打印一行英文」之后以为做完了,实际问题一个接一个,四类。

    一是中文打出来是乱码。我直接把字符串转成字节写进去,英文正常、中文全是乱码。原因是打印机默认按特定的中文编码解析,而我传的是另一种编码的字节。而小程序环境里没有现成的编码转换能力,这一步要自己解决。

    二是内容长一点就打出乱码或者只打一半。一张小票几百字节,我一次性写进去,结果是前面正常后面乱掉。查了才知道低功耗蓝牙的单次写入有长度上限,超出的部分设备根本收不到或者收乱了。而这个问题在我测「打印一行英文」时完全不出现——因为一行英文没超过上限。

    三是高峰期多单同时来,打印全乱。几个订单同时进来,我为每单各起一次写入,几路数据交叉写到同一个设备上,打出来的小票内容互相串了。

    四是打印失败没有任何反馈,后厨漏做单。打印机没纸了、被人碰掉了、蓝牙断了——我的实现里这些情况都是静默失败。商家不知道有单没打出来,后厨不知道要做这道菜,顾客等了半小时来问才发现。这是最严重的问题,因为它直接造成了经营损失。

    所以四个改动:中文做编码转换后再转字节按长度上限分包并串行写入加包间间隔加打印队列串行处理逐单记录打印状态并支持补打、连接状态常驻可见

    这个模块最想说的一句话是:对接硬件的时候,「调通一个最简单的例子」和「功能可用」之间隔着所有的边界条件。我打通「一行英文」花了半天,处理中文编码、分包、并发、失败反馈花了两周而这四个问题在最简单的例子里一个都不会出现——这也是为什么硬件对接类的活看起来简单实际不简单。

    整体链路
    设备连接 │ ├─ 初始化蓝牙适配器 → 搜索设备 → 按名称或服务标识过滤 │ 不过滤的话会搜出周围一堆无关设备,商家不知道选哪个 │ ├─ 连接 → 获取服务列表 → 找到可写的特征值 │ 特征值要判断属性支持写入,不同型号的服务标识不一样 │ ├─ 记住上次连接的设备,下次自动重连 │ 商家不该每天开店都重新选一次设备 │ └─ 连接状态常驻显示在界面顶部(已连接 / 断开 / 重连中) 不显示的后果:打印机离线了他不知道,直到顾客来问 内容拼装(打印指令) │ ├─ 初始化指令 → 逐段内容 → 走纸 → 切纸 │ ├─ 每段可设置:对齐方式 · 字号 · 加粗 · 下划线 │ 订单号和菜名要大字号(后厨要看得清) │ ├─ 分割线与对齐要按纸张宽度算字符数 │ 纸宽不同(常见两种规格)每行可容纳的字符数不同 │ 中文占两个字符宽,混排时要按宽度算而不是按字符数 │ └─ 中文编码转换(小程序环境没有原生能力) 字符串 → 目标编码字节 做法是内置精简码表按需查表,只覆盖常用字与符号 不转换的后果:英文正常、中文全乱码 分包写入(这是最关键的一环) │ ├─ 低功耗蓝牙单次写入有长度上限 │ 一张小票数百字节,一次性写入 → 后半段丢失或乱码 │ ├─ 按上限切分成多个包 │ ├─ 串行写入:上一包成功回调后再写下一包 │ 并行写入 → 设备端收到的顺序不可控 → 内容错乱 │ ├─ 包间加短间隔(设备处理需要时间) │ 没有间隔时部分型号会丢包 —— 表现为随机缺字 │ └─ 任一包失败 → 中止本次打印并标记失败(不要从中间续写) 从中间续写的后果:打出半张残票,后厨拿到会误解 打印队列(高峰期多单同时到) │ ├─ 打印请求进队列,串行出队处理 │ 并发写同一设备 → 多张小票内容互相串 │ ├─ 队列中的任务可见(还有几单待打) │ └─ 单个任务失败不阻塞后续任务,但要记录下来 失败处理与补打(最直接影响经营) │ ├─ 每单记录打印状态:成功 / 失败(原因)/ 待打印 │ ├─ 失败原因尽量具体:未连接 · 写入失败 · 缺纸(部分设备可读状态) │ ├─ 界面上把「有未成功打印的订单」做成显著提示 │ 静默失败的后果:后厨漏做单,顾客等半小时来问 │ ├─ 支持手动补打,补打带幂等标记 │ 避免重复出票(后厨拿到两张会做两份) │ └─ 断连时把待打印任务保留,重连后提示是否继续
    分步拆解
    1. 搜索设备要按名称或服务标识过滤。不过滤会搜出周围一堆无关的蓝牙设备(耳机、手环),商家不知道选哪个。
    2. 获取特征值时要判断是否支持写入。一个服务下有多个特征值,只有部分支持写入,写错了没有任何反应。不同型号的服务标识也不一样,要能配置。
    3. 要记住上次连接的设备并自动重连。商家不该每天开店都重新走一遍选设备的流程,这类摩擦会让他放弃使用。
    4. 连接状态必须常驻显示。打印机被碰掉了、没电了,不显示状态他不知道,直到顾客来问才发现漏单。
    5. 中文必须做编码转换再转字节。打印机按特定编码解析,直接传另一种编码的字节就是乱码。小程序环境没有原生转换能力,要自己内置码表。
    6. 码表可以只覆盖常用字与符号,不必全量。全量码表体积很大会拖慢启动,而小票上的字符范围是有限的(菜名、地址、姓名);对码表外的字符做降级处理(替换为占位符)并记录,用于后续补充。
    7. 排版要按显示宽度算而不是按字符数。中文占两个字符宽,混排时按字符数算会导致分割线和对齐全错。
    8. 纸张宽度要可配置。常见两种规格,每行能容纳的字符数不同,写死一种会让另一种规格的商家打出错位的小票。
    9. 必须按长度上限分包,这是最关键的一环。低功耗蓝牙单次写入有上限,一次性写几百字节会导致后半段丢失或乱码。而这个问题在「打印一行英文」的测试里完全不出现。
    10. 分包必须串行写入,上一包成功回调后再写下一包。并行写入的话设备端收到的顺序不可控,内容会错乱。
    11. 包间要加短间隔。设备处理需要时间,没有间隔时部分型号会丢包,表现为随机缺字——这种问题很难查,因为它是概率性的。
    12. 任一包失败要中止整次打印并标记失败,不要从中间续写。续写会打出半张残票,后厨拿到残票会误解(可能少做菜品)。
    13. 要有打印队列,串行处理。高峰期多单同时到,并发写同一设备会让多张小票的内容互相串。
    14. 队列状态要可见(还有几单待打)。让商家知道系统在处理,而不是以为卡住了去重复点。
    15. 每单要记录打印状态与失败原因。失败原因尽量具体(未连接、写入失败、缺纸),「打印失败」这个信息量太少,他不知道该怎么处理。
    16. 「有未成功打印的订单」要做成显著提示。这是防漏单的关键——静默失败会直接造成经营损失。
    17. 补打必须带幂等标记。避免重复出票,后厨拿到两张相同的小票会做两份。
    18. 断连时要保留待打印任务,重连后提示是否继续。直接丢弃会漏单,自动全部打出来又可能打一堆过期的单。
    关键决策与取舍

    内置精简码表而不是全量码表,也不是走服务端转换。全量码表体积很大,会明显拖慢小程序启动;走服务端转换(后端返回已编码的字节)能省本地体积,但引入了网络依赖——而打印恰恰是断网时也该能用的功能(订单已经在本地了,小票该能打出来)。精简码表的代价是可能遇到码表外的字符,所以要做降级(替换为占位符)并记录下来用于后续补充。判据是「这个功能能不能依赖网络」——打印是本地操作,不该被网络阻塞。

    分包串行 + 包间间隔,代价是打印速度变慢。并行写入理论上更快,但设备端收到的顺序不可控,内容会错乱——这不是性能问题而是正确性问题包间间隔的取舍更微妙:间隔越长越稳定但越慢。我的做法是在真实设备上试出一个「不丢包的最小间隔」,并且做成可配置——因为不同型号的处理速度不同,写死一个值会在某些设备上失效。这类「和硬件打交道的参数」我认为都应该可配置,因为你不可能测遍所有型号。

    失败时中止整张而不是从中间续写。续写能省掉重复打印的纸,但会产生半张残票——后厨拿到残票可能少做菜品,那个损失比一张纸大得多判据是「不完整的输出会不会被误用」——小票会被后厨当作生产指令,所以宁可重打整张。

    踩过的坑一:一次性写入几百字节,打出来前面正常后面乱掉。我最初完全不知道有单次写入长度上限这回事,因为我测试用的「打印一行英文」根本没超过上限这个坑让我明白:硬件对接里「最简单的例子能跑」几乎不提供任何保证——所有的边界条件(长度、编码、并发、时序)在最简单的例子里都不会出现。后来我做硬件相关的事,第一件事就是去查协议的各种限制,而不是先跑通一个例子。

    踩过的坑二:包间没有间隔,出现随机缺字。这个问题最难查,因为它是概率性的——同一张小票有时候正常有时候缺字。我一开始怀疑是编码问题、怀疑是内容里有特殊字符,查了很久。最后是在加了日志逐包记录之后,发现失败的位置是随机的,才想到是时序问题。教训是:概率性的问题通常和时序或并发有关,而不是和内容有关——这个判断能省很多时间。

    踩过的坑三:打印失败静默处理,后厨漏做单,顾客等了半小时。这是最严重的一个,因为它造成了真实的经营损失和顾客投诉。根因是我把打印当成了一个「尽力而为」的操作,失败了就算了。但对商家来说小票是生产指令,打不出来就等于这个订单不存在。修法是逐单记录状态、显著提示未打印订单、支持补打。教训是:判断一个操作能不能「失败了就算了」,要看它的下游有没有人依赖它的输出——有人依赖,就必须有失败反馈和补救手段。

    没做的部分:没做打印机状态的主动读取(缺纸、过热)。部分型号支持回读状态,但协议差异大、不是所有设备都支持,做成通用能力的成本很高。我的替代方案是「写入成功但商家反馈没出票」时提示他检查纸张——不完美但覆盖了主要场景。这个取舍我会诚实说明是能力限制而不是不需要。

    数字是怎么测的

    分包相关的数字要报清三件事:单包上限、一张小票的字节数与包数、包间间隔。比如「一张标准小票约几百字节,按上限切成十几包,包间间隔若干毫秒」。这几个数字一报,面试官就知道你真的处理过这个问题。

    成功率必须在真实设备上连续打印测:报「连续打印 N 张,成功 M 张、失败原因分布」。要说明测了几种型号——不同型号的表现差别很大,只测一台不能说明问题。

    包间间隔的取值要说清是怎么定的:「从较小值开始逐步增加,找到不再丢包的最小值,并留一定余量」。直接说一个数字而不说怎么来的,就是拍的。

    并发场景的验证:同时触发多个打印任务,断言每张小票内容完整且不互相混杂。这条要真的并发触发,不是顺序调用。

    编码正确性:准备一批包含常用汉字、标点、数字混排的小票内容,实际打印并逐字核对这一步只能人工核对,要诚实说明;同时记录码表未覆盖的字符出现频率。

    排版正确性:用不同纸张宽度和中英文混排内容,断言分割线对齐、不折行错位

    失败与补打:拔掉打印机电源制造失败,断言该单被标记为失败且界面出现显著提示;恢复后补打,断言只出一张票(幂等生效)

    不要报什么:不要报「打印成功率 100%」——硬件会缺纸、会断连,不可能百分百。该报的是「单包上限与实际分包数」「多型号下的连续打印成功率与失败原因分布」「包间间隔的确定方法」「并发打印内容不混杂」「失败可补打且幂等」这几件可核对的事。

    面试追问
    Q:蓝牙打印不就是调个 API 把内容写进去吗? A:我打通「打印一行英文」花了半天,处理完全部边界条件花了两周——而这些边界在最简单的例子里一个都不出现。四个具体问题。一是中文乱码:打印机按特定编码解析内容,直接把字符串转字节写进去,英文正常、中文全乱;而小程序环境没有原生的编码转换能力,我的做法是内置一份精简码表按需查表(只覆盖常用字与符号,全量码表体积太大会拖慢启动,码表外的字符做降级并记录)。二是长度上限:低功耗蓝牙单次写入有长度上限,一张小票几百字节,一次性写进去的结果是前面正常后面乱掉——必须按上限分包。三是分包必须串行且包间要有间隔:并行写入的话设备收到的顺序不可控、内容会串;而包间没有间隔时部分型号会丢包,表现为随机缺字四是高峰期多单同时到,几路数据交叉写到同一个设备,多张小票内容互相串——所以要有打印队列串行处理。我从这件事得到的最大教训是:硬件对接里「最简单的例子能跑」几乎不提供任何保证,所有的边界条件(长度、编码、并发、时序)在最简单的例子里都不会出现。后来我做硬件相关的事,第一件事是去查协议的各种限制,而不是先跑通一个例子。
    Q:随机缺字那个问题你是怎么定位的? A:这是我查得最久的一个问题,因为它是概率性的——同一张小票有时候正常、有时候中间缺几个字。我一开始的方向全错了:先怀疑是编码问题(是不是某些字不在码表里),逐字核对了码表;再怀疑是内容里有特殊字符(某些字节被当成了控制指令),把内容里的符号都替换过一遍。都不是。转折点是我加了日志逐包记录写入的内容和回调结果——然后发现缺字的位置是随机的,不固定在某个字或某个包。这个观察让我意识到问题不在内容上,而是在时序上:我在上一包的成功回调里立刻写下一包,但设备接收之后还需要时间处理,没等它处理完就写下一包,它就丢了。加上包间的短间隔之后问题消失。我总结出的判断经验是:概率性的问题通常和时序或并发有关,而不是和内容有关——如果是内容问题,同一份内容应该稳定复现同样的结果。这个判断能省很多时间,我当时在内容方向上浪费了两天。另外一个配套的决策:包间间隔我做成了可配置,并且是在真实设备上从较小值逐步增加、找到不再丢包的最小值再留余量。因为不同型号的处理速度不同,写死一个值会在某些设备上失效——而我不可能测遍所有型号。这类「和硬件打交道的参数」我认为都应该可配置。

模块二:接单出餐工作台与漏单防护

  1. 接单出餐工作台与漏单防护(未处理订单持续提醒而非只响一次 + 息屏与切后台的补偿拉取 + 多设备操作的并发保护 + 超时催单优先排序)★★★
    简历这样写 商家接单出餐工作台(uni-app + 长连接与轮询兜底 + 音频提醒 + 状态机):新订单提醒从只响一次改为未处理时按间隔持续提醒并随等待时长升级(原实现下高峰期店员正在忙,一次提示错过即漏单);针对小程序切后台被挂起、平板息屏等情况,回到前台时立即做一次补偿拉取并比对本地状态,把挂起期间产生的订单补齐并提醒,不依赖挂起中的定时器;订单状态流转收敛为显式状态机并对操作做并发保护(店里多台设备同时接单时,只有一次生效,其余给出明确提示而不是静默失败);列表默认按超时与催单优先排序而非下单时间,把最该处理的单顶到最前;关键操作(接单、出餐)本地先响应、失败回滚并提示,保证在网络抖动时界面不假成功。上线后漏单与超时未接单的反馈明显减少。
    展开完整拆解
    为什么要这么设计

    接单工作台第一版做得很直接:拉订单列表、新订单来了播一次提示音、每单下面「接单」「出餐」两个按钮。单人测试完全正常,放到真实门店里全是问题,四类。

    一是提示只响一次,高峰期必漏单。新订单来了响一声,但那个时候店员正在打包、在收钱、在跟顾客说话,一声过去就没了。而系统认为「我提醒过了」。结果是订单在那躺着没人接,超时之后顾客投诉、平台判罚

    二是设备息屏或者小程序被切到后台,回来之后少了几单。我用定时轮询拉新订单,但小程序切到后台会被挂起,定时器不再执行;平板息屏也一样。回到前台后我的定时器接着跑,只拉「最新的」,中间那段时间的订单没有被补上——它们既没有被提醒,也没有出现在我以为的位置上。

    三是店里两台设备同时操作,状态乱了。前台一台平板、后厨一台,两个人同时点了同一单的「接单」。我的实现里两个请求都返回成功(后端没做并发保护,前端也没有),状态被重复流转,还产生了两条操作记录。更糟的情况是一个点「接单」一个点「取消」,最终状态取决于哪个请求先到。

    四是列表按下单时间排序,最急的单在最下面。一个已经超时的单、一个顾客催过的单,因为下单早所以排在后面(我用的是倒序)。店员从上往下处理,最急的最后才看到。

    所以四个改动:未处理订单持续提醒并随等待时长升级回到前台立即补偿拉取并比对本地状态状态流转收敛为状态机并做并发保护默认按超时与催单优先排序

    这个模块最想说的一句话是:「我提醒过了」不等于「他知道了」。系统的责任不是「发出一次通知」,而是「确保这件事被处理,没被处理就继续提醒」。这个转变听起来是产品思路,但它对应的技术实现完全不同——前者是一次性事件,后者需要维护「未处理集合」并持续检查。

    整体链路
    订单获取(长连接为主 · 轮询兜底) │ ├─ 长连接推送新订单(实时性最好) │ ├─ 定时轮询兜底(长连接可能断、可能丢消息) │ 只靠长连接的后果:断了之后完全收不到新订单且不自知 │ └─ 每次拉取都带上本地最后同步的位置,服务端返回增量 只拉「最新 N 条」的问题:挂起期间的订单会被跳过 挂起与恢复(小程序切后台 / 平板息屏) │ ├─ 挂起中定时器不执行,长连接也会断 —— 这是平台限制不是 bug │ ├─ 回到前台立即做一次补偿拉取(不等下一个轮询周期) │ ├─ 拉回后与本地状态比对,找出挂起期间新增与变化的订单 │ 新增未处理的 → 立即提醒(不能因为「已经过去了」就不提醒) │ └─ 恢复长连接 整个恢复流程要幂等 —— 可能被多次触发(反复切前后台) 持续提醒(防漏单的核心) │ ├─ 维护「未处理订单集合」,而不是「新订单事件」 │ 事件是一次性的,集合是持续存在的 —— 这是设计上的关键区别 │ ├─ 集合非空时按间隔持续提醒 │ 间隔要随等待时长缩短(越久越急) │ ├─ 提醒强度分级 │ 提示音 → 音量更大 / 更急促的音 → 界面全局横幅 → 持续震动 │ ├─ 集合清空则停止(接单后自动停) │ └─ 提醒不能被静音键完全屏蔽(这是生产工具,要在能力范围内保证触达) 同时给一个显式的「静音」开关并记录 —— 不给的话他会关掉整机声音 状态流转(多设备并发) │ ├─ 显式状态机:待接单 → 已接单 → 制作中 → 待取/待配送 → 完成 │ 取消与退款是另外的分支,同样纳入状态机 │ ├─ 操作请求带「当前状态」作为前置条件 │ 服务端校验:状态不符则拒绝并返回最新状态 │ 不带前置条件的后果:两台设备同时操作都成功,状态重复流转 │ ├─ 被拒绝时前端要明确提示「该订单已被其他设备处理」 │ 静默失败或只提示「操作失败」都会让店员反复点 │ └─ 本地先响应 + 失败回滚 网络抖动时不能假成功 —— 他以为接单了,实际没有 排序(把最该处理的顶到最前) ├─ 优先级:已超时 > 顾客催过 > 预约时间临近 > 普通新单 ├─ 同级内按下单时间正序(先来先做) └─ 按下单时间倒序的问题:最急的单排在最下面 其他 ├─ 操作按钮要有防重复点击(提交中禁用) ├─ 重要操作(取消订单)要二次确认 └─ 界面要在强光下可读(店里环境),字号与对比度都要放大
    分步拆解
    1. 把「新订单事件」改成维护「未处理订单集合」,这是防漏单的核心设计。事件是一次性的(响一声就没了),集合是持续存在的(只要还有未处理的就一直提醒)。这个区别决定了会不会漏单。
    2. 提醒间隔要随等待时长缩短。等得越久越急。固定间隔的问题是要么一直很吵、要么对紧急的单不够急。
    3. 提醒强度要分级升级。提示音 → 更急促的音 → 全局横幅 → 持续震动。单一强度要么不够引起注意、要么一开始就很扰人。
    4. 要给显式的静音开关并记录。不给的话店员会直接关掉设备声音,那就完全失去提醒能力了——给一个可控的开关,比让他用不可控的方式绕过要好。
    5. 长连接为主、轮询兜底,两者都要有。只靠长连接的问题是断了之后完全收不到新订单而且不自知;只靠轮询的问题是实时性差。
    6. 拉取要带「本地最后同步位置」让服务端返回增量。只拉「最新 N 条」的话,挂起期间产生的订单会被跳过——这是我漏单的直接原因。
    7. 要认清「挂起中定时器不执行」是平台限制而不是 bug。小程序切后台、平板息屏都会挂起。所以不能依赖挂起期间的任何定时逻辑,只能在恢复时补偿。
    8. 回到前台要立即补偿拉取,不等下一个轮询周期。等周期意味着又延迟了一段时间,而这段时间可能就是超时的临界。
    9. 补偿拉取后要与本地状态比对,新增的未处理订单要立即提醒。不能因为「这单是几分钟前来的」就不提醒——它还没被处理,就仍然需要提醒。
    10. 恢复流程要幂等。反复切前后台会多次触发,不幂等会产生重复提醒或重复请求。
    11. 状态流转要收敛成显式状态机。散落的 if 判断在多设备并发下会产生不可枚举的状态组合。
    12. 操作请求要带「当前状态」作为前置条件,由服务端校验。这是并发保护的关键——不带前置条件的话,两台设备同时操作都会成功,状态被重复流转。
    13. 被拒绝时要明确提示「该订单已被其他设备处理」。只提示「操作失败」的话,店员会反复点,以为是网络问题。
    14. 关键操作要本地先响应加失败回滚。网络抖动时不能假成功——他以为接单了实际没有,那就变成了另一种漏单。
    15. 默认排序要按「已超时 > 催过 > 预约临近 > 普通」而不是下单时间。按时间倒序的话最急的单排在最下面,店员从上往下处理,最后才看到它。
    16. 同优先级内按下单时间正序(先来先做)。这符合公平预期,也符合门店的实际操作习惯。
    17. 操作按钮要防重复点击(提交中禁用)。店员在忙的时候会连点。
    18. 界面要考虑门店的物理环境:强光、距离远、手上有油。字号和对比度都要比常规后台更大,点击区域也要更大。
    关键决策与取舍

    持续提醒的代价是「吵」,这是必须接受的。产品最初担心商家嫌吵,但漏单的代价(顾客投诉、平台判罚、门店评分下降)远大于吵我的处理是让吵变得可控而不是取消它:间隔随等待时长递进(刚来的不急、久了才急)、强度分级、并给一个显式的静音开关且记录静音时长。关键判断是「如果不给显式的静音开关,他会用不可控的方式绕过」——直接关掉设备声音,那时候我们就完全失去提醒能力了。给一个可控的出口,比堵死所有出口更安全。

    长连接与轮询都做,不是二选一。只做长连接的问题不是「实时性不够」而是「断开了之后不自知」——它会安静地失效。只做轮询的问题是实时性差且请求浪费。两者叠加的代价是实现复杂一些、要处理重复推送(靠订单标识去重)。判据是「漏一单的代价」——在这个场景下漏单代价很高,所以值得付双份的实现成本。

    并发保护选「带前置状态由服务端校验」而不是「前端锁」。前端锁只能防同一台设备的重复点击,防不住两台设备。带前置状态的代价是每次操作要传当前状态、被拒绝时要处理最新状态的回显。判据很简单:真实场景里就是多台设备(前台一台、后厨一台),所以并发保护必须在服务端。

    踩过的坑一:提示只响一次,高峰期漏单。这是最直接造成业务损失的问题。我的思维误区是把提醒当成了「事件通知」——发出去就完成了我的责任。但对商家来说系统的责任是「确保这件事被处理」。修法是从「新订单事件」改成「维护未处理订单集合并持续提醒」。这个转变听起来是产品思路,但它对应的技术实现完全不同:前者是一次性的回调,后者要维护集合状态、要有定时检查、要处理集合的增删。我认为这是这个模块最有价值的一点。

    踩过的坑二:小程序切后台被挂起,回来之后少了几单。我的轮询定时器在挂起期间不执行,而恢复后我只拉「最新 N 条」,中间那段的订单被跳过了——它们既没被提醒也没出现在列表该有的位置。教训有两层:一是要知道平台会挂起你的代码(这是限制不是 bug,不能靠「让定时器继续跑」来解决);二是增量同步必须带位置游标,「拉最新 N 条」在有间断的场景下一定会丢数据。

    踩过的坑三:两台设备同时接单,两个请求都成功。状态被重复流转,还产生了两条操作记录。更糟的是一个点接单一个点取消时,最终状态取决于哪个请求先到——完全不可控。修法是操作带前置状态由服务端校验。教训是:只要用户可能在多个设备或多个标签页上操作,前端的任何互斥都是无效的,必须在服务端做。

    没做的部分:没做离线接单(断网时先在本地接单,恢复后同步)。它能覆盖门店断网的场景,但风险很高——本地接单后如果订单已被顾客取消或被系统超时关闭,同步时会冲突,而这个冲突的处理涉及资金和履约,不该由客户端决定我的替代方案是断网时明确提示「网络异常,请勿依据当前列表出餐」,把风险显式暴露给商家而不是假装能用。

    数字是怎么测的

    漏单率是这个模块的核心指标,定义要说清:我们用「超时未接单的订单数占比」作为代理指标。要说明这个指标受门店忙碌程度影响,所以要看同一批门店改造前后的对比,而不是不同门店之间比。

    提醒有效性用「从订单到达到被接单的中位时长」衡量。持续提醒之后这个时长应该下降。用中位数,因为少数极端值会让平均值失真。

    挂起恢复的验证是断言型用例:切后台一段时间、期间注入若干新订单、切回前台,断言这些订单全部出现在列表中且触发了提醒这条要真的切后台测,在开发工具里模拟不一定准,要在真机上做。

    并发保护:用两个客户端同时对同一订单发起接单,断言只有一个成功、另一个收到明确的「已被其他设备处理」提示且界面刷新到最新状态

    长连接断开的兜底验证:人为断开长连接,断言轮询仍能拉到新订单并提醒。这条最容易漏测,因为正常情况下长连接是好的。

    本地先响应的失败回滚:让接单请求失败,断言界面状态回滚到「待接单」并给出提示,而不是停留在「已接单」。假成功比失败更危险。

    排序正确性:构造包含超时单、催单、预约单、普通单的数据,断言排序符合优先级规则、同级内按时间正序

    不要报什么:不要报「漏单率降为零」——门店可能整个店都在忙,系统提醒再多也没人看。该报的是「同批门店超时未接单占比的前后对比」「订单到接单的中位时长下降」「切后台期间的订单全部补齐并提醒」「并发操作只有一个生效且另一个有明确提示」这几件可核对的事。

    面试追问
    Q:新订单来了播个提示音就行了,为什么要做持续提醒?商家不会觉得烦吗? A:会觉得烦,产品最初也担心这个,但漏单的代价大得多,所以我的选择是让「烦」变得可控而不是取消它。先说为什么必须持续:只响一次的问题是高峰期店员正在打包、在收钱、在跟顾客说话,一声过去就没了,而系统认为「我提醒过了」。结果订单躺在那没人接,超时之后顾客投诉、平台判罚、门店评分下降。我的思维误区在于把提醒当成了「事件通知」——发出去就完成了我的责任。但对商家来说,系统的责任是「确保这件事被处理,没被处理就继续提醒」这个转变对应的技术实现完全不同:原来是监听「新订单事件」(一次性回调),改成维护一个「未处理订单集合」,集合非空就按间隔持续提醒,集合清空自动停止——要维护集合状态、要定时检查、要处理集合的增删。至于「烦」,我做了三件事让它可控:间隔随等待时长递进(刚来的不急、久了才急)、强度分级升级(提示音 → 更急促的音 → 全局横幅 → 震动)、以及给一个显式的静音开关并记录静音时长最后这条很关键:如果不给显式开关,店员会直接关掉整机声音——那时候我们就完全失去提醒能力了,而且不知道他关了给一个可控的出口,比堵死所有出口更安全。
    Q:小程序切到后台订单收不到,这不是平台限制吗,你能做什么? A:挂起本身是平台限制,我改不了;但「回来之后订单丢了」是我的实现问题,这两件事要分开。我最初的实现是定时轮询拉新订单,挂起期间定时器不执行(这是限制),恢复后定时器接着跑——但我拉的是「最新 N 条」问题就出在这里:挂起那段时间产生的订单,既没有被提醒,也因为「最新 N 条」的窗口滑过去了而没出现在列表里,等于凭空消失。修法有两点。一是拉取要带「本地最后同步位置」让服务端返回增量,而不是拉最新 N 条——「拉最新 N 条」在任何有间断的场景下都一定会丢数据,这是我学到的通用教训。二是回到前台立即做一次补偿拉取,不等下一个轮询周期(等周期又延迟一段时间,而这段时间可能就是超时的临界),拉回后与本地状态比对,把挂起期间新增的未处理订单找出来并立即提醒——不能因为「这单是几分钟前来的」就不提醒,它还没被处理就仍然需要提醒。另外整个恢复流程要幂等,因为反复切前后台会多次触发。我想强调的是思路上的转变:面对平台限制,不要试图对抗它(比如想办法让定时器在后台继续跑),而是承认它然后设计恢复机制——挂起是必然发生的,那就保证恢复后能补齐。顺带说,同样的道理让我在长连接之外还保留了轮询兜底:长连接断开的问题不是实时性不够,是它会安静地失效而你不自知

模块三:营业状态与菜品估清

  1. 营业状态与菜品估清(打烊前在途订单的显式处理 + 估清在多设备间的实时同步 + 数量型与售罄型的区分 + 次日自动恢复与手动兜底)★★
    简历这样写 营业状态与估清管理(uni-app + 长连接同步 + 乐观更新 + 定时任务配合):打烊操作原为直接置为休息,未处理在途订单,改为打烊前检查并显式列出未完成订单(提示需先完成或改约,避免顾客已下单却查到门店休息);估清支持售罄标记与剩余数量两种模式(原仅有售罄开关,无法表达「还剩三份」这类常见诉求),数量归零自动转售罄;店内多台设备并存,估清与营业状态改为长连接实时同步 + 前台恢复时补偿拉取,解决前台标记售罄而后厨设备仍显示可售、以及顾客点到已售罄菜品的问题;操作采用本地先响应、失败回滚并提示,并对多设备同时修改做版本校验(冲突时提示最新值而非静默覆盖);估清次日自动恢复并保留手动批量恢复入口。上线后售罄相关的退单与客诉明显下降。
    展开完整拆解
    为什么要这么设计

    这一块最初的实现只有两个开关:一个「营业中 / 休息中」,每个菜品一个「售罄」勾选。逻辑上没有任何复杂度,问题全出在它和门店真实经营的错配上,四类。

    一是打烊之后在途订单没人管。老板到点直接点「打烊」,但还有几单已经付款、还没做完。门店状态变成休息后,顾客在 App 上查到「门店已休息」,以为自己的单不做了,打电话来问;而店里也可能真的忘了这几单(列表被打烊状态影响了展示)。

    二是只能标「售罄」,表达不了「还剩几份」。这是老板反复提的需求:某道菜还剩三份,他想让这三份卖完自动售罄,而不是靠自己盯着。用只有布尔值的开关,他的实际做法是估摸着提前手动点售罄——那三份就卖不出去了,是实实在在的损失。

    三是店里多台设备状态不一致。前台平板上标了某道菜售罄,后厨那台设备上还显示可售(因为它上次拉数据是十分钟前)。更严重的是顾客端也有延迟,顾客点了已经售罄的菜,做不出来只能退单。

    四是估清第二天要手动一个个恢复。昨天售罄的十几道菜,今天开店要逐个取消售罄。老板经常忘,结果是当天这些菜一直卖不出去,他到中午才发现。

    所以四个改动:打烊前检查并显式列出在途订单估清支持数量模式且归零自动转售罄状态改为长连接实时同步加前台恢复补偿估清次日自动恢复并保留手动批量入口。另外补了多设备同时修改的版本校验

    这个模块最想说的一句话是:一个开关能不能表达用户的真实意图,决定了这个功能是帮他还是逼他做变通。只有「售罄」这个布尔开关的时候,老板的应对是提前手动点售罄——这是明确的经营损失,而且我们从数据上看不出来(只看到「他点了售罄」,看不到「他其实还有三份没卖」)。用户的变通行为往往是需求没被满足的信号,而它不会出现在工单里。

    整体链路
    营业状态 │ ├─ 手动开店 / 打烊 │ ├─ 打烊前检查(这一步原来完全没有) │ 列出未完成的在途订单(已付款未完成) │ 提示:需先完成这些订单,或联系顾客改约 / 退款 │ 不检查的后果:顾客查到门店休息,以为自己的单不做了 │ ├─ 按营业时间自动开关店(配合营业时间配置) │ 自动切换要给通知,不要静默 —— 他需要知道店已经开了 │ ├─ 临时休息(不改营业时间,只是当下暂停接单) │ 和「打烊」语义不同 —— 打烊是今天结束,临时休息是稍后继续 │ └─ 状态变更要同步到顾客端并有明确的展示文案 「休息中」和「今日已结束」对顾客的含义不同 估清(两种模式,覆盖不同意图) │ ├─ 模式一:售罄标记(布尔) │ 适用于「今天这道菜不卖了」 │ ├─ 模式二:剩余数量 │ 适用于「还剩三份,卖完自动下架」 │ 每成单扣减,归零自动转售罄 │ 没有这个模式的后果:老板提前手动点售罄,剩下的卖不掉 │ ├─ 数量扣减必须由服务端保证(不能由客户端算) │ 多个顾客同时下单,客户端算必然超卖 │ └─ 支持批量操作(一次标记多个菜品) 收摊时要标一批,逐个点太慢 多设备同步(店里通常两台以上) │ ├─ 长连接推送状态变更(前台改了,后厨立刻看到) │ ├─ 前台恢复(切前后台 / 息屏唤醒)时补偿拉取一次 │ 不能只依赖长连接 —— 挂起期间会断 │ ├─ 操作本地先响应 + 失败回滚 + 明确提示 │ 网络抖动时不能假成功 —— 他以为标了售罄,实际没有 │ └─ 多设备同时修改同一菜品 → 版本校验 带上本地版本号,服务端版本不符则拒绝并返回最新值 提示「该菜品已被其他设备修改,当前为剩余 2 份」 静默覆盖的后果:后厨改的数量被前台的旧数据冲掉 次日恢复 │ ├─ 估清默认次日自动恢复(定时任务在开店前执行) │ 不自动恢复的后果:老板忘记取消售罄,整天卖不出去 │ ├─ 支持标记为「长期售罄」(不参与自动恢复) │ 比如季节性下架的菜品,自动恢复反而是错的 │ └─ 保留手动批量恢复入口(自动恢复失败时的兜底) 可观测 ├─ 售罄时长统计(哪些菜经常售罄 → 备货建议) ├─ 因售罄导致的退单数(这个数字直接说明同步是否及时) └─ 打烊时存在在途订单的次数(说明检查是否起作用)
    分步拆解
    1. 打烊前必须检查并显式列出在途订单。不检查的后果是顾客已付款却查到门店休息,以为单不做了;店里也可能真的忘了处理这几单。
    2. 提示要给出可操作的选项(先完成、联系顾客改约、退款),而不是只说「还有未完成订单」——后者他不知道该怎么办。
    3. 「打烊」和「临时休息」要区分成两个操作。语义不同:打烊是今天结束,临时休息是稍后继续。混成一个的话,顾客看到的文案也就无法区分——而「休息中」和「今日已结束」对顾客的含义完全不同。
    4. 按营业时间自动开关店时要给通知,不要静默。老板需要知道「店已经开了,可以开始接单了」。
    5. 估清要支持「剩余数量」模式,这是收益最直接的改动。只有布尔开关时,老板的应对是提前手动点售罄,剩下的份数卖不掉——这是实实在在的经营损失。
    6. 数量归零要自动转售罄。这才是「剩余数量」模式的意义所在——不用他盯着。
    7. 数量扣减必须由服务端保证,不能由客户端算。多个顾客同时下单,客户端算必然超卖。这是正确性问题不是体验问题。
    8. 估清要支持批量操作。收摊时要标一批菜品,逐个点太慢,他就不标了。
    9. 状态变更要通过长连接实时推送到店内其他设备。前台标了售罄后厨立刻看到——不同步的后果是后厨按旧数据备料。
    10. 不能只依赖长连接,前台恢复时要补偿拉取一次。挂起期间长连接会断,这一点和接单工作台是同一个问题。
    11. 操作要本地先响应加失败回滚加明确提示。网络抖动时不能假成功——他以为标了售罄,实际没标,顾客继续点单。
    12. 多设备同时修改要做版本校验。带上本地版本号,服务端版本不符则拒绝并返回最新值。静默覆盖的后果是后厨改的数量被前台的旧数据冲掉。
    13. 冲突提示要带上最新值。「该菜品已被其他设备修改,当前为剩余 2 份」——只说「修改失败」他不知道现在到底是多少。
    14. 估清默认次日自动恢复。不自动恢复的后果是老板忘记取消售罄,整天卖不出去,到中午才发现。
    15. 要支持「长期售罄」标记,不参与自动恢复。季节性下架的菜品自动恢复反而是错的。一个默认行为要留出例外的口子。
    16. 自动恢复要保留手动批量兜底入口。定时任务可能失败,而失败的表现是「菜品一直售罄」——他需要能自己救回来。
    17. 因售罄导致的退单数要做成监控指标。这个数字直接说明同步是否及时——顾客点到已售罄的菜,说明估清没有及时传到顾客端。
    关键决策与取舍

    数量扣减放在服务端而不是客户端。客户端扣减实现简单、响应快,但多个顾客同时下单时必然超卖——而超卖的后果是退单和客诉,代价远大于那点响应速度判据是「这个计算的结果会不会被并发影响」:会的话就必须在有唯一裁决者的地方算。客户端可以做乐观显示,但真实扣减由服务端定。

    多设备冲突选「版本校验 + 提示最新值」而不是「后写覆盖」。后写覆盖实现最简单,但会出现「后厨把数量改成 2,前台用十分钟前的旧数据点了个操作,数量被改回 5」这种情况——而没人知道发生了什么。版本校验的代价是要处理冲突提示、用户要重新操作一次。判据是「静默覆盖的后果是否可察觉」——这里不可察觉(数量默默变回旧值,直到超卖才暴露),所以必须显式拒绝。

    估清默认次日自动恢复,而不是保持售罄直到手动取消。「保持」更保守(不会误恢复),但实际情况是老板经常忘记恢复,整天卖不出去——而这个损失他自己也感知不到(他不知道那道菜今天一份都没卖)自动恢复的风险是「本来就该长期下架的菜被恢复了」,所以配了「长期售罄」标记作为例外。判据是「哪种默认行为的错误代价更小、更容易被发现」:误恢复了他会看到菜品在售、可以立刻再标;忘记恢复则完全无感知。

    踩过的坑一:打烊没有检查在途订单。顾客已付款、订单还没做完,他在 App 上查到「门店已休息」,以为单不做了,打电话来问;客服也不知道怎么解释。教训是:任何「切断服务」的操作,都要先检查有没有正在进行中的事情——这和「删除数据前要检查引用」是同一类思维。我当时只想到了「打烊 = 改一个状态」,没想到这个状态会被顾客解读成「我的订单不做了」。

    踩过的坑二:只做了布尔的售罄开关,老板的应对是提前手动点售罄。而这个变通行为在数据上完全看不出来——我们只看到「他点了售罄」,看不到「他其实还有三份没卖」。是老板在一次沟通里说的:「我怕卖超了不好意思跟客人说,所以剩几份的时候我就直接点售罄了」。教训是:用户的变通行为往往是需求没被满足的信号,而它不会出现在工单里——因为他不认为这是系统的问题,他认为这是他自己的应对办法。所以要主动去问「你平时是怎么处理这种情况的」,而不是等他来提需求。

    踩过的坑三:多设备状态不同步,顾客点到已售罄的菜。前台标了售罄,但后厨设备和顾客端都还是旧数据,顾客下单成功,做不出来只能退单。教训是:只要同一份数据会在多个终端上被查看和修改,「同步」就不是可选的优化而是功能的一部分。我最初把它当成了「刷新一下就好」的小问题,实际它直接造成了退单。

    没做的部分:没做基于历史销量的备货与估清建议。它有价值(能提前提醒「这道菜通常这个点会卖完」),但需要销量预测能力,而且建议错了会误导备货我做的是最基础的版本:统计各菜品的售罄时长与频率给老板看,让他自己判断——提供事实而不是提供结论,这在我这个层级是更合适的选择。

    数字是怎么测的

    「因售罄导致的退单数」是这个模块最有说服力的指标。它直接对应「估清是否及时同步到顾客端」。报法是改造前后同批门店的对比,并说明统计口径(退单原因是怎么归类的)。

    多设备同步延迟要实测并报清测法:在 A 设备标记售罄,记录 B 设备与顾客端反映变化的时间要分两种情况报:长连接正常时(应在秒级内)、长连接断开走补偿拉取时(取决于恢复时机)。只报前者是不完整的。

    数量模式的正确性要做并发测试:剩余数量设为 3,并发下 5 单,断言只有 3 单成功、其余提示售罄,且最终数量为零并自动转售罄这条必须真并发,顺序下单测不出超卖。

    版本冲突:两个客户端各持旧版本同时修改,断言一个成功、另一个被拒绝并收到最新值

    打烊检查:构造存在在途订单的场景,断言打烊被拦下并列出了这些订单;处理完之后可正常打烊。

    次日自动恢复:断言普通售罄被恢复、标记为「长期售罄」的没有被恢复。第二条是例外路径,最容易漏测。

    本地先响应的回滚:让标记售罄的请求失败,断言界面回滚为「在售」并提示,而不是停留在「已售罄」——假成功会让他以为标了。

    不要报什么:不要报「估清准确率」——这取决于老板自己标得准不准,不是系统能力。该报的是「因售罄导致的退单数前后对比」「长连接正常与断开两种情况下的同步延迟」「并发下不超卖」「长期售罄不被自动恢复」这几件可核对的事。

    面试追问
    Q:估清加一个「剩余数量」听起来只是多个字段,为什么说它收益最大? A:因为它解决的是一个实实在在的经营损失,而这个损失我们原来在数据上完全看不出来。原来只有布尔的「售罄」开关。老板的实际做法是:某道菜还剩三份的时候,他就提前手动点售罄了——那三份就卖不出去。而我们从数据上只看到「他点了售罄」,看不到「他其实还有三份没卖」。这个事是他在一次沟通里说的:「我怕卖超了不好意思跟客人说,所以剩几份的时候我就直接点售罄了」。所以加「剩余数量」模式不是多一个字段,是让系统能表达他的真实意图(这三份卖完自动下架),从而不用他提前放弃这三份我从这件事得到的教训是:用户的变通行为往往是需求没被满足的信号,而它不会出现在工单里——因为他不认为这是系统的问题,他认为这是他自己的应对办法。所以要主动去问「你平时是怎么处理这种情况的」,而不是等他来提需求。实现上有一个必须说清的点:数量扣减必须由服务端保证,不能由客户端算——多个顾客同时下单,客户端算必然超卖,而超卖的后果是退单和客诉,代价远大于那点响应速度。客户端可以做乐观显示,但真实裁决在服务端。另外数量归零要自动转售罄,这才是这个模式的意义——不用他盯着。
    Q:店里两台设备状态不一致,让他手动刷新一下不就行了? A:我最初就是这么想的,把它当成「刷新一下就好」的小问题,结果它直接造成了退单。具体的链条是:前台平板上标了某道菜售罄,后厨那台设备还显示可售(上次拉数据是十分钟前),更严重的是顾客端也有延迟——顾客点了已经售罄的菜,做不出来只能退单指望「他手动刷新」不成立的原因有两个:一是他不知道数据是旧的(界面上没有任何提示说「这是十分钟前的数据」),所以他没有刷新的动机;二是顾客端根本不在他的控制范围内——顾客不会为了看菜品是否售罄而刷新页面。所以我的结论是:只要同一份数据会在多个终端上被查看和修改,「同步」就不是可选的优化而是功能的一部分。实现上做了三层:长连接实时推送(前台改了后厨秒级看到)、前台恢复时补偿拉取一次(挂起期间长连接会断,不能只依赖它)、以及多设备同时修改的版本校验最后这层容易被忽略但很重要:如果用「后写覆盖」,会出现「后厨把数量改成 2,前台用十分钟前的旧数据点了个操作,数量被改回 5」——而没人知道发生了什么,直到超卖才暴露。所以要带版本号,服务端版本不符就拒绝,并且提示里要带上最新值(「该菜品已被其他设备修改,当前剩余 2 份」)——只说「修改失败」他不知道现在到底是多少。我的判据是「静默覆盖的后果是否可察觉」:不可察觉的,就必须显式拒绝。

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

项目拆解 · 门店经营端(uni-app · 实习级)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据