项目背景:HalfCart 半仓智拼,一个基于 Vue 3 + FastAPI 的同城拼团电商系统,集成高德地图 SDK 提供定位与选点能力。

问题现象:用户在长沙打开首页,左上角定位地址始终显示"北京市海淀区三里屯133号"。进入地图选点页面却能正确定位到长沙。

调试耗时:7 轮迭代,横跨前端、后端、Docker、Redis 四个技术层。


一、问题复现:一个"诡异"的地址

这是一个典型的"看起来简单,实际上牵扯全链路"的 Bug。现象非常明确:

  • 首页:左上角定位地址写死显示"北京市海淀区三里屯133号"
  • 地图选点页:能正确定位到用户所在城市(长沙)
  • 环境:桌面端浏览器,HTTP 协议(非 HTTPS),Docker Compose 部署

同一个系统,两个页面,定位能力表现完全不同。这说明问题不在定位引擎本身,而在首页的定位调用链路中某个环节出了问题。

在动手修复之前,先梳理一下首页定位的完整调用链路:

失败

成功

用户打开首页

浏览器 navigator.geolocation

IP 定位兜底

拿到坐标 lat/lng

调用后端 /api/v1/lbs/regeo

后端 amap_client.regeo

Redis 有缓存?

直接返回缓存地址

调用高德逆地理编码 API

写入 Redis 缓存

前端显示地址

这条链路有 7 个环节可能出问题。排查的核心思路是:从前端到后端,逐层验证每一环的输入输出,找到断点


二、第一轮:前端定位逻辑——硬编码的北京坐标

2.1 怀疑点

首页定位是否根本没有调用浏览器定位 API,而是用了硬编码的默认坐标?

2.2 排查过程

阅读 frontend/src/index.html 中的 getLocation() 函数,找到了第一处问题:

function getLocation() {
    var userLat = 39.99, userLng = 116.47;  // 北京天安门附近
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
            function(position) {
                userLat = position.coords.latitude;
                userLng = position.coords.longitude;
                initRadarSearch(userLat, userLng);
            },
            function(error) {
                // 定位失败,直接用默认坐标(北京)
                initRadarSearch(userLat, userLng);
            },
            { timeout: 5000 }
        );
    } else {
        initRadarSearch(userLat, userLng);
    }
}

代码逻辑:先定义默认坐标(北京天安门),尝试调用浏览器定位。如果定位失败或超时,直接用默认坐标发起后续查询。

问题在于:桌面端浏览器在 HTTP 环境下,navigator.geolocation.getCurrentPosition 几乎必然失败——现代浏览器要求 HTTPS 才能使用 GPS 定位 API。这意味着 5 秒超时后,代码静默回退到北京坐标。

2.3 修复

给浏览器定位加 8 秒超时保护,超时后标记"定位超时"而非静默使用北京坐标:

var userLat = null, userLng = null;  // 不再硬编码默认值

navigator.geolocation.getCurrentPosition(
    function(position) {
        userLat = position.coords.latitude;
        userLng = position.coords.longitude;
        initRadarSearch(userLat, userLng);
    },
    function(error) {
        console.warn('[定位] 浏览器 GPS 失败:', error.message);
        // 不再静默回退到北京坐标,交给 IP 定位兜底
        fallbackToIPLocation();
    },
    { timeout: 8000, enableHighAccuracy: true }
);

2.4 结论

未解决。这是代码质量优化,消除了硬编码默认值,但不是根因。即使定位成功拿到正确坐标,地址仍然显示北京。

技术洞察:浏览器 navigator.geolocation API 在非 HTTPS 环境下会被禁用或受限。这是 W3C 安全规范的要求——位置信息属于敏感数据,明文 HTTP 传输存在被中间人窃听的风险。生产环境必须部署 HTTPS。


三、第二轮:后端 IP 定位降级——永远返回北京 Mock 数据

3.1 怀疑点

浏览器 GPS 在桌面端 HTTP 环境下不可用,需要一个 IP 定位兜底方案。但后端 IP 定位是否正常工作?

3.2 排查过程

GPS 失败后,前端调用后端 /api/v1/lbs/ip-locate 做城市级定位。后端使用高德 IP 定位 API,根据请求的来源 IP 返回用户所在城市。

@router.get("/ip-locate")
async def ip_locate(request: Request):
    # 获取客户端真实 IP
    client_ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip()
    if not client_ip:
        client_ip = request.client.host

    # 调用高德 IP 定位
    result = await amap_service.ip_locate(client_ip)
    return {"code": 0, "data": result}

直接用 cURL 测试这个接口:

curl http://localhost:8000/api/v1/lbs/ip-locate
# 响应:{"code": 0, "data": {"city": "北京市", "district": "海淀区", ...}}

后端 IP 定位永远返回北京。

3.3 根因分析

后端运行在 Docker 容器内,它看到的"客户端 IP"是 Docker 网桥的网关地址(172.x.x.x),不是用户的真实公网 IP。高德 IP 定位 API 拿到一个内网 IP,无法识别,默认返回了北京的数据。

用户浏览器 (公网 IP: 1.2.3.4)
    │
    ▼
Nginx (宿主机)
    │ X-Forwarded-For: 1.2.3.4
    ▼
Docker 网桥 (172.17.0.1)  ← 后端看到的 IP
    │
    ▼
FastAPI 容器 (172.17.0.2)
    request.client.host = "172.17.0.1"  ← 不是用户真实 IP!

虽然 Nginx 配置了 X-Forwarded-For 头,但 FastAPI 需要正确配置 --proxy-headers 才能信任这个头。

3.4 结论

未解决。后端 IP 定位方案在 Docker 架构下存在天然缺陷:容器内无法直接获取用户真实 IP,除非正确配置代理头传递链路。

技术洞察:在 Docker + Nginx 反向代理架构中,后端应用获取用户真实 IP 需要两个条件:(1) Nginx 正确设置 X-Forwarded-ForX-Real-IP 头;(2) 后端框架配置为信任代理头(如 FastAPI 的 uvicorn --proxy-headers,Django 的 SECURE_PROXY_SSL_HEADER)。


四、第三轮:浏览器端 IP 定位——被 GFW 阻断的境外服务

4.1 怀疑点

既然后端 IP 定位有问题,那让前端直接调用第三方 IP 定位服务,绕过 Docker 网络层。

4.2 排查过程

前端直接调用 https://ipapi.co/json/ 获取用户真实 IP 的城市信息:

async function fallbackToIPLocation() {
    try {
        const resp = await fetch('https://ipapi.co/json/');
        const data = await resp.json();
        if (data.latitude && data.longitude) {
            initRadarSearch(data.latitude, data.longitude);
        }
    } catch (e) {
        console.error('[定位] IP 定位失败:', e);
    }
}

本地开发环境测试通过,能正确返回长沙坐标。部署到服务器后,用户反馈地址仍然显示北京。

4.3 根因分析

ipapi.co 是一个境外服务,在中国大陆被 GFW 阻断。fetch 请求在浏览器端发出后,TCP 连接被重置或超时,catch 捕获到错误,但此时没有任何兜底逻辑,页面继续保持上一次的地址(北京)。

// 浏览器控制台日志
GET https://ipapi.co/json/ net::ERR_CONNECTION_TIMED_OUT
[定位] IP 定位失败: TypeError: Failed to fetch

4.4 结论

未解决。境外服务在国内不可靠,这是一个常被忽略的部署环境差异。

技术洞察:在本地开发时能访问的服务,不代表国内生产环境也能访问。涉及定位、地图等业务,应优先选择国内服务提供商(高德、百度、腾讯)。跨境服务的稳定性受网络环境影响极大,不应作为核心链路的依赖。


五、第四轮:高德 JSAPI 浏览器端定位——坐标对了,地址还是错

5.1 怀疑点

高德 JSAPI v2.0 已在项目中加载(用于地图选点页面),其 AMap.Geolocation 插件支持 GPS + WiFi + IP 三通道定位,全程走国内网络,应该能解决定位问题。

5.2 排查过程

首页改用 AMap.Geolocation 替代浏览器原生 navigator.geolocation

const amapGeolocation = new AMapLib.Geolocation({
    enableHighAccuracy: true,    // 启用高精度定位
    timeout: 8000,               // 8 秒超时
    GeoLocationFirst: true,      // 优先使用 GPS
});

amapGeolocation.getCurrentPosition(function(status, result) {
    if (status === 'complete' && result.position) {
        const userLat = result.position.lat;
        const userLng = result.position.lng;
        console.log('[定位] 坐标:', userLat, userLng);
        reverseGeocode(userLat, userLng);
    }
});

5.3 关键发现

控制台日志显示坐标已经是长沙:

[定位] 坐标: 28.219864 112.903904
GET /api/v1/lbs/regeo?lat=28.219864&lng=112.903904

坐标 28.22°N, 112.90°E 确实是长沙岳麓区。但前端页面仍然显示北京地址!

5.4 结论

未解决。定位引擎返回了正确坐标,但地址显示依然错误。问题出在"坐标→地址"的反查环节(reverseGeocode),而不是定位环节。

技术洞察:定位(获取坐标)和逆地理编码(坐标转地址)是两个独立的环节。定位正确不代表地址显示正确。排查这类问题时,需要明确区分"坐标是否正确"和"地址是否正确"两个子问题,分别验证。


六、第五轮:发现 Mock 模式仍在运行——Docker restart 的陷阱

6.1 怀疑点

坐标正确,但后端 regeo API 返回的是北京地址。可能是后端高德 API 的 Mock 模式仍在运行。

6.2 排查过程

检查 .env 文件,配置已经是 AMAP_MOCK_ENABLED=false。但进入容器直接查询运行时配置:

docker exec halfcart-backend python -c \
  "from app.config import settings; print(settings.AMAP_MOCK_ENABLED)"
# 输出: True  ← 问题!

.env 文件改了,但容器内的应用仍然读取到 True

6.3 根因分析

这是一个 Docker 的经典陷阱。之前修改 .env 后,只执行了 docker compose restart 重启容器。但 docker restart 只是重启容器内的进程,不会重新读取环境变量

Docker 容器的环境变量是在容器创建时注入的,存储在容器的配置元数据中。restart 相当于"重启进程",容器配置不变;up --force-recreate 相当于"销毁旧容器、用新配置创建新容器",环境变量才会更新。

# docker compose restart 的行为:
# 1. 停止容器内的进程
# 2. 重新启动进程
# 3. 容器配置(包括环境变量)不变 ← .env 的修改被忽略

# docker compose up --force-recreate 的行为:
# 1. 销毁旧容器
# 2. 读取最新的 docker-compose.yml 和 .env
# 3. 创建新容器,注入新的环境变量 ← .env 的修改生效

6.4 修复

docker compose up -d --force-recreate backend consumer

验证容器内的配置已更新:

docker exec halfcart-backend python -c \
  "from app.config import settings; print(settings.AMAP_MOCK_ENABLED)"
# 输出: False ✅

直接在容器内测试 Python 函数,确认 API 调用正常:

await amap.regeo(112.904, 28.219)
# 返回: "湖南省长沙市岳麓区麓谷街道..." ✅

6.5 结论

仍未解决。后端 Python 函数直接调用返回正确地址,但通过 HTTP API 调用时,前端仍然收到北京地址。

技术洞察docker compose restartdocker compose up --force-recreate 是两个完全不同层次的操作。前者是进程级重启,后者是容器级重建。修改了 docker-compose.yml.env 后,必须使用 --force-recreate。这是 Docker Compose 最常见的陷阱之一。


七、第六轮:高德 IP 地址不可信——formattedAddress 的谎言

7.1 怀疑点

前端的 AMap.Geolocation 回调中,result.formattedAddress 字段来自高德 IP 库,可能对用户 IP 返回了错误的城市文本。

7.2 排查过程

仔细审查前端代码的逻辑分支:

amapGeolocation.getCurrentPosition(function(status, result) {
    if (status === 'complete' && result.position) {
        // result.position = {lat: 28.219, lng: 112.904}  ← 坐标正确(长沙)
        // result.formattedAddress = "北京市海淀区三里屯133号"  ← IP 库错误!

        if (result.formattedAddress) {
            // 走了这个分支!直接用了 IP 库的文本地址
            currentLocation.value = result.formattedAddress;
        } else {
            // 这行永远不会执行
            reverseGeocode(userLat, userLng);
        }
    }
});

AMap.GeolocationgetCurrentPosition 回调中,result 对象同时包含 position(坐标)和 formattedAddress(文本地址)。当定位方式是 IP 定位时,formattedAddress 来自高德的 IP 地址库,而不是根据坐标逆地理编码得到的。高德 IP 库对这个 IP 的归属判断有误,返回了北京地址。

7.3 修复

始终用坐标调用后端 regeo API,不信任高德 IP 库的文本地址:

amapGeolocation.getCurrentPosition(function(status, result) {
    if (status === 'complete' && result.position) {
        const userLat = result.position.lat;
        const userLng = result.position.lng;
        // 忽略 result.formattedAddress,始终走后端逆地理编码
        reverseGeocode(userLat, userLng);
    }
});

7.4 结论

仍未解决。代码已改为始终走 reverseGeocode 后端 API,但页面仍显示北京地址。

技术洞察AMap.Geolocation 的回调结果是一个"混合体"——坐标来自 GPS/WiFi/IP 三通道融合定位,但文本地址可能来自 IP 地址库(精度低、可能有偏差)。在使用第三方定位 SDK 时,应区分"可信数据"(坐标)和"参考数据"(IP 文本地址),关键业务始终用坐标做逆地理编码。


八、第七轮:添加调试日志,发现真凶——Redis 缓存投毒

8.1 怀疑点

代码改为始终走后端 reverseGeocode API,但地址仍然是北京。后端 Python 函数直接测试返回正确地址,但 HTTP API 返回错误地址。一定存在某个中间层在作祟。

8.2 排查过程

reverseGeocode.then() 回调中添加 console.log,直接看后端 HTTP API 到底返回了什么数据:

function reverseGeocode(lat, lng) {
    fetch(`/api/v1/lbs/regeo?lat=${lat}&lng=${lng}`)
        .then(resp => resp.json())
        .then(data => {
            console.log('[regeo] response:', data);
            console.log('[regeo] setting address to:', data.data?.formatted_address);
            currentLocation.value = data.data?.formatted_address;
        });
}

用户控制台输出:

[regeo] response: {code: 0, message: '查询成功', data: {…}}
[regeo] setting address to: 北京市海淀区三里屯133号

后端 API 返回的 formattedAddress 就是"北京市海淀区三里屯133号"!

但刚才在容器内直接测 Python 函数 await amap.regeo(112.904, 28.219) 返回的是长沙地址。同一个函数,同一个参数,通过 HTTP 调用和直接调用的结果不同!

8.3 定位真凶:缓存层

这种"同函数不同结果"的现象,几乎可以断定存在缓存层。阅读 amap_client.py 中的 regeo 方法:

async def regeo(self, lng: float, lat: float) -> Optional[dict]:
    # 1. 计算坐标哈希作为缓存 Key
    coord_hash = self._hash(f"{lng:.6f}", f"{lat:.6f}")
    cache_key = _CACHE_REGEO_KEY.format(hash=coord_hash)

    # 2. 先查 Redis 缓存
    cached = await self._cache_get(cache_key)
    if cached:
        return cached  # ← 缓存命中,直接返回!

    # 3. 缓存未命中,调用真实高德 API
    result = await self._call_amap_regeo_api(lng, lat)

    # 4. 写入缓存
    await self._cache_set(cache_key, result, ttl=_REGEO_CACHE_TTL)

    return result

问题在第 2 步:缓存命中时直接返回缓存数据,不再调用真实 API。

为什么缓存里是北京地址?

在 Mock 模式开启期间(第一到第五轮之间),这个坐标的 regeo 请求被 Mock 拦截,返回了虚假的北京地址。这个虚假结果被写入了 Redis 缓存。Mock 关闭后,同一个坐标的请求命中缓存,直接返回了 Mock 时期写入的假数据。

验证缓存内容:

# 查看所有 regeo 缓存
docker exec halfcart-redis redis-cli -a $REDIS_PASS KEYS "*regeo*"
# 输出:
# 1) "halfcart:amap:regeo:779672fa893c1bf4"
# 2) "halfcart:amap:regeo:0d540fa12b9b7bdc"
# 3) "halfcart:amap:regeo:acb29486a6d132e0"
# 4) "halfcart:amap:regeo:b8aff51d8c3be775"
# 5) "halfcart:amap:regeo:d96b34ecb7f33073"

# 查看其中一条缓存的值
docker exec halfcart-redis redis-cli -a $REDIS_PASS GET "halfcart:amap:regeo:779672fa893c1bf4"
# 输出: {"formatted_address": "北京市海淀区三里屯133号", ...}

真凶确认:Mock 模式期间,这组坐标被反向地理编码为虚假的北京地址,结果写入了 Redis 缓存。关闭 Mock 后,同坐标的请求命中缓存,直接返回了旧数据。而直接在 Python 中测试函数时,使用的是不同的坐标精度(未经过 _hash 规范化),命中了不同的缓存 Key,所以返回了正确结果。

8.4 完整的问题链路

① 浏览器 GPS 失败
(HTTP 环境不支持)

② ipapi.co 被 GFW 阻断
(境外服务不可达)

③ 高德 IP 定位返回假地址文本
(IP 库归属判断有误)

④ 但坐标是正确的
(28.22°N, 112.90°E = 长沙)

⑤ 后端 regeo Mock 模式开启
(docker restart 未更新 env)

⑥ Mock 假数据写入 Redis 缓存
(北京地址被缓存)

⑦ Mock 关闭后缓存仍命中
(始终返回假的北京地址)

最终现象:始终显示北京地址

图中橙色节点是"缓存投毒"的核心环节,红色节点是最终的现象。


九、最终修复:清除污染缓存

9.1 清除所有 regeo 缓存

docker exec halfcart-redis redis-cli -a $REDIS_PASS \
  DEL halfcart:amap:regeo:779672fa893c1bf4 \
      halfcart:amap:regeo:0d540fa12b9b7bdc \
      halfcart:amap:regeo:acb29486a6d132e0 \
      halfcart:amap:regeo:b8aff51d8c3be775 \
      halfcart:amap:regeo:d96b34ecb7f33073
# 输出: (integer) 5  ← 删除了 5 条缓存

9.2 验证修复

刷新页面后,控制台日志:

[定位] 坐标: 28.219864 112.903904
[regeo] response: {code: 0, message: '查询成功', data: {formatted_address: "湖南省长沙市岳麓区麓谷街道..."}}
[regeo] setting address to: 湖南省长沙市岳麓区麓谷街道...

问题解决。首次请求缓存未命中 → 调用真实高德 API → 返回正确长沙地址 → 写入新缓存 → 后续请求命中新缓存。


十、技术总结与教训

10.1 问题链路的六个环节

环节 问题 技术层 解决方式
浏览器 GPS 在 HTTP 下不可用 前端 改用高德 JSAPI 定位
后端 IP 定位拿到 Docker 网关 IP 后端/Docker 前端直连 IP 定位服务
ipapi.co 被 GFW 阻断 网络 改用国内服务(高德)
高德 IP 文本地址与坐标不一致 前端 忽略 IP 文本,用坐标做 regeo
docker restart 不更新 env Docker 使用 --force-recreate
Mock 数据污染 Redis 缓存 缓存 清除缓存,修复 TTL

前五个环节是"干扰项",第六个才是真正的根因。但如果没有前五轮的排查,无法定位到第六个环节。

10.2 六条核心教训

教训一:docker restart 不等于 docker up --force-recreate

这是本轮调试耗时最长的干扰项。docker compose restart 只是重启容器进程,容器创建时注入的环境变量不会更新。修改了 .envdocker-compose.yml 后,必须使用 docker compose up -d --force-recreate 重建容器。

验证方式:进入容器内部直接查询运行时配置,而不是只看 .env 文件。

# 不要只看 .env 文件
cat .env | grep MOCK

# 要看容器内实际生效的值
docker exec <container> python -c "from app.config import settings; print(settings.AMAP_MOCK_ENABLED)"

教训二:缓存是调试的盲区

这是本轮调试的核心教训。直接在 Python 中测试函数返回正确结果,但通过 HTTP API 调用返回错误结果——这种"同函数不同结果"的现象,几乎一定存在缓存层。

缓存的危险性在于:它在 Mock 模式期间被"投毒"(写入了假数据),Mock 关闭后仍然存活。如果缓存的 TTL 设置过长或未设置,污染数据会长期存在。

排查缓存问题的方法:

  1. 直接查询 Redis 中的缓存内容
  2. 对比"缓存命中"和"缓存未命中"两种情况的结果差异
  3. 清除缓存后重新测试

教训三:调试日志是最后的利器

在第六轮之前,所有排查都依赖代码审查和推理。第七轮在 reverseGeocode 回调中加了 console.log,立刻确认了"后端 API 返回的就是北京地址"这个关键事实,从而把排查范围从前端缩小到后端。

调试日志的价值在于消除假设——不再猜测"可能是前端的问题"或"可能是后端的问题",而是用数据说话。

// 永远不要省略调试日志,尤其是在异步回调链中
fetch(url)
    .then(resp => resp.json())
    .then(data => {
        console.log('[调试] API 返回:', data);  // ← 关键!
        process(data);
    });

教训四:不要信任 IP 定位的文本地址

高德 AMap.Geolocation 的回调结果中,formattedAddress 字段可能来自 IP 地址库(精度到城市级),而不是根据坐标逆地理编码得到的(精度到街道级)。IP 库的归属判断可能有误,导致坐标是长沙、文本地址却是北京。

正确做法:只信任坐标(position.lat/position.lng),文本地址始终通过坐标调用逆地理编码 API 获取。

教训五:境外服务在国内不可靠

ipapi.co 在本地开发环境能访问,但在国内生产环境被 GFW 阻断。涉及定位、地图等业务,应优先选择国内服务提供商。在架构设计阶段就应该评估服务的可用地域,而不是部署后才发现不可用。

教训六:Mock 数据应有明确标识

如果 Mock 数据带有 (Mock) 后缀或特殊标记,在页面上能一眼识别"这是假数据",排查会快很多。建议在 Mock 模式下,所有返回的地址都加上明显标识:

if settings.AMAP_MOCK_ENABLED:
    result["formatted_address"] = f"[MOCK] {result['formatted_address']}"

10.3 涉及的技术层全景

前端 (Vue 3 + Vant 4)
  ├── navigator.geolocation    ← 浏览器 GPS(HTTP 下不可用)
  ├── AMap.Geolocation         ← 高德 JSAPI 定位(坐标可信,IP 文本不可信)
  ├── ipapi.co                 ← 第三方 IP 定位(被 GFW 阻断)
  └── reverseGeocode()         ← 坐标反查(调用后端 API)
         ↓
Nginx 反向代理
         ↓
后端 (FastAPI)
  ├── /api/v1/lbs/regeo        ← 反向地理编码 API
  └── /api/v1/lbs/ip-locate    ← IP 定位 API
         ↓
服务层
  ├── amap_service.regeo_location()
  └── amap_client.regeo()      ← ← ← 真凶所在:缓存层
         ↓
缓存层 (Redis)
  └── halfcart:amap:regeo:*    ← Mock 数据污染了缓存
         ↓
外部 API (高德 Web Service)
  └── 逆地理编码 API

十一、后续改进建议

11.1 Mock 数据加统一标识

在 Mock 模式下,所有返回数据加上明显的前缀或后缀,让开发者在页面上能一眼识别:

if settings.AMAP_MOCK_ENABLED:
    result["formatted_address"] = f"[MOCK - 请检查 AMAP_MOCK_ENABLED 配置] {mock_address}"

11.2 缓存 TTL 合理化

regeo 缓存应设置合理的过期时间。地址信息不会频繁变化,但也不应该永久缓存。建议 TTL 设为 24 小时,平衡缓存命中率和数据新鲜度:

_REGEO_CACHE_TTL = 60 * 60 * 24  # 24 小时
await self._cache_set(cache_key, result, ttl=_REGEO_CACHE_TTL)

11.3 环境变量变更检测

在 Docker entrypoint 中打印关键配置,便于确认容器实际使用的值:

#!/bin/bash
echo "=== 环境变量检查 ==="
echo "AMAP_MOCK_ENABLED: ${AMAP_MOCK_ENABLED}"
echo "AMAP_API_KEY: ${AMAP_API_KEY:0:8}..."
echo "==================="
exec "$@"

11.4 健康检查增强

/health 端点返回 Mock 状态,让前端在开发模式下显示警告横幅:

@router.get("/health")
async def health():
    return {
        "status": "ok",
        "amap_mock_enabled": settings.AMAP_MOCK_ENABLED,
        "version": settings.APP_VERSION
    }

11.5 Mock 模式自动清缓存

在关闭 Mock 模式的过程中,自动清除相关缓存,避免污染数据残留:

async def disable_mock_mode():
    """关闭 Mock 模式时自动清除所有 LBS 相关缓存"""
    await redis.delete_pattern("halfcart:amap:*")
    logger.info("已清除所有高德 API 缓存")

11.6 HTTPS 部署

生产环境启用 HTTPS 后,浏览器 GPS 精度会大幅提升(支持 WiFi 定位和 GPS 定位),减少对 IP 定位的依赖。同时 HTTPS 也解决了 navigator.geolocation 不可用的问题。


十二、调试方法论复盘

回顾这 7 轮调试,可以提炼出一条通用的全栈 Bug 排查方法论:

未找到

找到

1. 明确现象
区分'定位错'还是'地址错'

2. 梳理调用链
画出完整的数据流图

3. 逐层验证
每一层的输入输出是否符合预期

4. 添加调试日志
用数据消除假设

5. 找到异常点

6. 分析根因
为什么会这样?

7. 修复并验证
清除污染数据,确认修复生效

8. 总结教训
如何防止同类问题再次发生

核心原则:不要跳层,不要假设,用日志说话

每一轮排查都遵循"假设 → 验证 → 结论"的闭环。即使结论是"未解决",每一轮也都缩小了问题范围,排除了一个可能性。这种"排除法"在全链路调试中是最可靠的方法。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐