最初的实现是「进页面 → 请求定位 → 拿到坐标 → 请求附近门店 → 渲染」。这个串行链路在真实环境下问题很大。
一是定位很慢甚至拿不到。室内、地下商场、开了省电模式的手机,定位可能要十几秒甚至超时失败。用户看到的是长时间白屏,然后一个「定位失败」的提示,页面什么都没有。
二是权限被拒绝就等于功能不可用。用户第一次进来被弹权限框,很多人会直接点拒绝(尤其是没有说明为什么要定位的时候)。拒绝之后页面完全空白,用户只能退出。
三是地图标注点太多导致卡顿。把附近所有门店都打到地图上,密集区域几百个标注点,低端机上拖动地图明显卡。
核心判断是:定位是一个「不可靠的外部依赖」,不能把它当成必然成功的前置步骤。所有和外部世界打交道的能力(定位、摄像头、网络)都要按「随时可能失败」来设计。所以做成多级降级链,任何一级能拿到一个可用的位置就先渲染。
用过期缓存位置的风险。用户从一个城市飞到另一个城市,缓存位置还是原来的城市,会看到完全不相关的门店。所以缓存要带时间戳并且有过期判断,超过一定时间的缓存只用来快速渲染但同时强制发起新定位,并且明确标注「基于上次位置」。取舍是「先给内容」优于「给准确内容」,但必须让用户知道这是不准确的。
为什么不把定位做成阻塞的。阻塞的好处是数据一定准确,不会出现「先展示一批然后跳变」的情况。但代价是白屏时间不可控(依赖外部系统),而且失败时完全没内容。我们的判断是:不可靠的外部依赖不能放在关键路径上。这个原则在定位、支付回调、三方审核这些场景都适用,是可以横向迁移的经验。
踩过的坑:小程序端定位授权后仍然拿不到坐标。用户明确同意了权限,但接口返回失败。排查发现是系统级的定位服务被关闭了(不是应用权限,是手机的 GPS 总开关)。应用权限和系统定位开关是两件事,只判断前者会误判。修法是失败时区分错误码——权限被拒绝、系统定位未开启、定位超时,三种情况给完全不同的引导文案。笼统的「定位失败请重试」对用户毫无帮助。
踩过的坑二:坐标系不一致导致门店位置偏移几百米。定位接口返回的坐标系和地图组件使用的坐标系不同,直接把坐标打到地图上,门店位置整体偏移。这类问题肉眼很难第一时间发现(地图上看起来是对的,只是偏了一点),是用户反馈「导航到的地方不对」才定位到。修法是明确每个环节的坐标系并统一转换,并且在测试用例里加入已知地点的坐标校验。
没做的部分:没做定位结果的平滑处理。用户在移动中(比如坐车),定位会持续跳动导致距离数字不停变化。理想做法是做位置的平滑滤波和更新节流,我们只是简单地降低了更新频率。
首屏可见时间:在页面 onLoad 和门店列表首次渲染处打时间戳。要分场景报:缓存位置命中时 1 秒内出内容;首次安装(无缓存)时仍然要等定位,这种情况下的耗时取决于定位本身,前端优化不了。只报好的场景是选择性呈现,会被追问「第一次进来呢」。
定位耗时分布:这个值得埋点——记录定位从发起到返回的耗时,看分布。会发现长尾很长(P95 可能是好几秒,还有一部分直接超时),这个数据正是「不能把定位放在关键路径上」的证据。用数据支撑设计决策,比说「定位可能很慢」有力得多。
不要报「定位成功率从 X% 提升到 Y%」。定位成功率主要由设备、环境、系统决定,前端改不了。前端能改的是「定位失败时功能是否可用」,这个用定性描述——改造前定位失败页面空白,改造后有降级内容。
地图性能:可以报标注点数量的控制效果(改造前密集区域几百个标注、拖动明显掉帧;改造后聚合到几十个以内、拖动流畅)。要报测试机型,因为这类问题只在低端机上显现。
核销是本地生活里最容易出纠纷的环节,因为它一次操作对应真实的商品或服务交付。出错的后果很具体:重复核销导致用户的券被多扣一次;核销失败但商家已经把东西给了,商家亏损。
第一版的实现是「扫码 → 提交核销 → 显示结果」。问题在店内环境集中爆发:
一是店内网络极差。很多店铺在商场内部或者地下,网络时断时续。提交核销经常转圈很久,店员会重复点。二是店员操作节奏很快——高峰期一分钟核销十几单,任何等待都是压力,而且他们会连续快速扫码,容易出现上一单还没确认就扫了下一单。三是设备是低端机,扫码识别慢、界面响应慢。
核心判断和支付链路一样:「未知」是必须显式处理的第三态。但核销比支付多一个约束——它必须能在弱网甚至短时断网下继续工作,因为店员不能因为网络问题就停止营业。所以引入了本地待同步队列。
为什么不做真正的「离线核销」(断网时直接判定成功)。这个需求商家一定会提——「网断了我也要能核销」。但技术上不能给出真正的离线核销,因为客户端无法验证券的有效性和是否已被使用,如果允许离线成功,同一张券可以在多台设备上离线核销多次,恢复后才发现冲突,那时东西已经给出去了。我们的方案是「离线可操作,但状态是待确认」——店员的操作不被阻塞(可以继续接待下一个客人),但界面明确告知这笔还没确认,商家自己决定要不要先交付。这个取舍要讲清楚:我们解决的是「操作不中断」,不是「离线也能确认」。把这两件事混为一谈是设计上的错误。
「待确认」状态给商家带来的心理成本。商家会问「待确认到底能不能给东西」。这不是技术问题而是业务规则问题——需要和业务方定清楚:低客单价的可以先交付(风险可控),高客单价的等确认。前端要做的是把状态和金额都清楚展示,让商家有信息做决定。承认「有些问题的答案不在技术层面」,是成熟度的体现。
踩过的坑:店员连续快速扫码导致串单。上一笔还在提交中,店员已经扫了下一张码,界面上的券信息被新的覆盖,店员看到的核销成功提示对应的是哪一笔说不清了。修法是把核销做成明确的队列而不是单个当前项——每次扫码创建一条独立记录进列表,各自显示自己的状态,界面上是一个列表而不是一个「当前券」。这个改动是交互模型的改变,不是加个 loading 能解决的。
踩过的坑二:扫码在低端机上识别很慢。店员要举着手机对好几秒。优化了几点:降低扫码的预览分辨率(识别不需要高清)、限制识别频率(不用每帧都识别)、加对焦提示和取景框(引导用户把码放在中心)、支持手动输入券码兜底(码磨损或者摄像头坏了还能用)。手动输入这个兜底很重要,它是「物理设备可能失效」的应对。
没做的部分:没做商家端的核销数据本地统计(断网期间店员看不到今日核销笔数汇总)。理论上待同步队列的数据可以本地统计,但涉及和服务端数据的对齐,当时没做完整。
重复核销:脚本模拟弱网加快速重复提交同一张券(限速后连点 5 次),检查服务端产生了几条核销记录、券被扣了几次。改造前每轮可复现,改造后未再复现。这类正确性问题必须用可复现的测试来说明,用线上比率反而不可信(分母不好定,而且量小时统计不显著)。
断网恢复的验证:手工演练——开飞行模式,连续核销 3 笔(都进待确认),恢复网络,验证 3 笔都自动确认成功且没有重复。要能说出演练步骤和预期结果,这比说「我做了离线队列」有说服力。
扫码识别耗时:在低端机上测「从摄像头对准码到识别成功」的时间,多次取中位数。要报机型。这个指标店员感知最直接,也是最容易被商家投诉的点。
不要报「核销成功率」。核销失败的主要原因是券本身无效或已使用,这是正常业务结果不是系统问题,把它算进成功率会得出一个没有意义的数字。可以报的是「未知态最终被正确确认的比例」或者更实在的「异常单据数量」。
这个模块服务两个场景:商家上传门店和商品图、用户发布到店评价(带图)。最初的实现是「选完图后循环调上传接口」,问题很多。
一是原图太大。现在手机拍的照片单张三四兆,九张就是近三十兆。在店内的弱网环境下这基本传不完。二是循环上传没有并发控制——九张图同时发起,互相抢带宽,每一张都变慢,而且小程序有并发请求上限,超出的直接失败。三是一张失败整批失败,用户要重新选图重新传,非常挫败。四是应用被切后台或杀掉,已上传的进度全丢。
所以四个方向对应四个改造:本地压缩(从源头减少数据量,收益最大)、受控并发队列、单张独立重试、本地持久化草稿。
其中本地压缩是投入产出比最高的——它把问题规模降了一个数量级,比任何传输层的优化都有效。这个判断和视频上传是一样的:能减少数据量就先减少数据量。
压缩质量的取舍。压太狠画质肉眼可见地糊,商家会抱怨门店照片不好看;压太轻起不到作用。做法是按业务场景分档——商品主图和门店门面照要求高一些(压得轻),评价配图和凭证照可以压得狠一些。不要一套参数打天下,不同用途的图对画质的要求差很多。
水印放客户端还是服务端。客户端加水印的好处是所见即所得、不增加服务端成本;坏处是可以被绕过(用改包工具直接传没水印的图)。所以判断依据是水印的用途:如果是为了美观和信息展示(时间、门店),客户端加没问题;如果是作为凭证的防篡改水印(比如证明某张照片是在某时某地拍的),必须服务端加,客户端加的不可信。这个区分要说出来,说明理解「客户端不可信」这个前提。
踩过的坑:Canvas 压缩后图片旋转 90 度。手机拍的照片带 EXIF 的方向信息,直接绘制到 Canvas 时部分端会忽略这个信息,导出的图是躺倒的。修法是先读取方向信息,绘制前做对应的旋转变换。这个坑在不同端表现不一致(有的端的 Canvas 会自动处理,有的不会),所以必须在每个端都实测。这也是跨端开发的典型问题——同一段代码在不同端行为不同。
踩过的坑二:大图在低端机上 Canvas 处理导致崩溃。一张四千万像素的图绘制到 Canvas,内存占用是宽乘高乘 4 字节,低端机直接被系统杀掉。修法是先判断原图尺寸,超过阈值的分步缩放(先缩到中间尺寸,再缩到目标尺寸),避免一次性创建巨大的 Canvas。这个坑说明「压缩本身也要考虑资源消耗」——为了省流量而崩溃是得不偿失的。
没做的部分:没做上传的后台续传(应用切后台时继续上传)。App 端理论上可以做(要处理系统的后台执行限制),小程序端做不到。当时的处理是切后台时暂停、回前台时继续,配合草稿持久化保证不丢。
上传体积:取一批真实照片样本(要覆盖不同场景——室内暗光、室外强光、细节丰富的菜品图),统计压缩前后的平均大小。样本量要说得出来(比如 30 张),不然「平均」没有意义。改造前平均约 3.2MB,改造后 300KB 上下。
弱网整批完成率:用限速工具把带宽限到 256KB/s,同一批九张图重复上传 20 次,统计整批全部成功的次数。用「不足半数」和「多数场景可完成」这种模糊量级而不是精确百分比——20 次的样本量算出 45% 这种精度是假的。
可以补充的指标:单张上传的耗时中位数、失败后重试的成功率、草稿恢复的使用频次。其中「草稿恢复被用到的次数」很有说服力——它直接证明这个功能是真实需要的,不是过度设计。
不要报「图片上传成功率 99%」。这类高精度的比率一被追问统计方式就说不清(分母是什么?重试算一次还是多次?),而且 99% 这个数字在弱网场景下不真实。
没有匹配的内容,换个关键词试试。
项目拆解 · 本地生活到店(uni-app)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据