Claude Code 安全实现 Geolocation API:权限、备用输入、隐私与测试
用 Claude Code 安全实现 Geolocation API:权限体验、HTTPS、备用输入、隐私与测试。
位置功能很容易被低估。一个“查找附近门店”的按钮,看起来只是调用一次浏览器 API,但实际会牵涉权限弹窗、HTTPS、安全上下文、用户拒绝、超时、手动地址输入、地图服务边界、日志脱敏以及自动化测试。
如果只让 Claude Code“加一个当前位置按钮”,它很可能写出能跑的 getCurrentPosition 片段,却没有说明为什么要请求位置,也没有处理拒绝、没有备用输入,甚至把经纬度直接打进日志。公开产品不能这样交付。
本文以初学者也能照做的方式,讲清楚如何用 Claude Code 实现浏览器 Geolocation API。参考的一手资料包括 MDN Geolocation API、getCurrentPosition、watchPosition、Permissions API、W3C Geolocation 规范、Chrome 对安全来源的要求、Chrome DevTools Sensors、Playwright emulation 和 Claude Code permissions。站内可以继续阅读 地图集成、安全审计 和 响应式设计。
先划清功能边界
Geolocation API 的职责只有一个:让浏览器尝试返回当前设备的位置。位置来源可能是 GPS、Wi-Fi、蜂窝网络、IP、系统定位服务或缓存结果。应用无法保证使用哪一种来源,也不能假设返回值一定是用户的真实精确位置。
因此,写代码前要先做四个决定。第一,只在用户主动点击时请求权限,不要在页面加载时弹窗。第二,默认不要启用高精度,除非业务真的需要。第三,必须准备邮编、城市、地址或门店名的手动输入。第四,先决定是否保存位置;很多场景只需要计算结果,不需要保存原始经纬度。
| 决策 | 推荐做法 | 常见错误 |
|---|---|---|
| 请求时机 | 用户点击“使用当前位置”后 | 打开页面立刻请求权限 |
| 精度 | 初次使用 enableHighAccuracy: false | 所有场景都强制高精度 |
| 失败处理 | 提供邮编或地址输入 | 把拒绝当成异常并卡住流程 |
| 日志 | 记录事件和粗粒度分组 | console.log(position) 直接泄露坐标 |
地图也要分层。Geolocation 返回坐标;地图渲染、地址转坐标、路线规划、附近地点搜索属于 Google Maps、Mapbox、OpenStreetMap 或后端服务。让 Claude Code 改代码时,要明确“浏览器位置模块只返回坐标,不碰地图 API key,不直接调用付费地图服务”。
具体产品场景
第一个场景是门店搜索。用户点击按钮后,应用用当前位置给门店排序。安全做法是在按钮旁边放“按邮编/城市搜索”,这样拒绝权限的用户仍然能完成任务。
第二个场景是配送范围与到达时间。生鲜、电商、上门维修、租赁服务都可能需要判断用户是否在服务区内。多数情况下,后台只需要保存“是否在服务区”“距离分组”或“可预约时段”,而不是保存精确坐标。
第三个场景是现场作业签到。清洁、运维、活动工作人员、销售拜访可以用位置确认是否到达指定地点。但定位失败时不能直接阻断工作,应提供照片、主管审批、手写备注等备用流程。
第四个场景是本地内容。天气、附近活动、库存门店、城市公告都可以和位置相关。不过如果只需要城市级别,用户手动选择城市往往比请求精确定位更合适,也更容易建立信任。
可直接运行的 getCurrentPosition 示例
下面保存为 geo-demo.html,通过 localhost 或 HTTPS 打开。它包含权限说明、超时、缓存位置、手动备用输入和简单的脱敏显示。
<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Geolocation demo</title>
<style>
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
margin: 2rem;
}
button,
input {
font: inherit;
padding: 0.7rem 0.9rem;
}
.panel {
border: 1px solid #ddd;
max-width: 36rem;
padding: 1rem;
}
</style>
</head>
<body>
<main class="panel">
<h1>查找附近门店</h1>
<p>
我们只用当前位置排序附近门店,
不保存精确地址或移动轨迹。
</p>
<button id="useLocation" type="button">使用当前位置</button>
<p id="status" role="status" aria-live="polite"></p>
<pre id="result"></pre>
<form id="manualForm">
<label for="postcode">邮编、城市或地址</label>
<input id="postcode" name="postcode" autocomplete="postal-code" />
<button type="submit">手动搜索</button>
</form>
</main>
<script type="module">
const status = document.querySelector("#status");
const result = document.querySelector("#result");
const button = document.querySelector("#useLocation");
const form = document.querySelector("#manualForm");
function showManual(reason) {
status.textContent =
`${reason}。也可以用邮编、城市或地址搜索。`;
}
function onSuccess(position) {
const { latitude, longitude, accuracy } = position.coords;
status.textContent = "已获取当前位置。";
result.textContent = JSON.stringify(
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracyMeters: Math.round(accuracy),
},
null,
2,
);
}
function onError(error) {
const messages = {
1: "位置权限被拒绝",
2: "设备位置暂时不可用",
3: "位置请求超时",
};
showManual(messages[error.code] ?? "无法获取位置");
}
button.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
showManual("当前浏览器不支持 Geolocation");
return;
}
status.textContent = "正在确认位置权限...";
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: false,
timeout: 8000,
maximumAge: 60000,
});
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
status.textContent =
`将以“${data.get("postcode")}”为中心搜索。`;
});
</script>
</body>
</html>
timeout 是等待上限,maximumAge 是可接受的缓存位置时长,enableHighAccuracy 是向浏览器请求更高精度。门店搜索通常不需要高精度;现场签到可以把缓存时间缩短,并在 UI 上告诉用户正在进行一次新的定位。
watchPosition 与停止追踪
watchPosition 用于持续接收位置变化。它适合配送员路线、运动记录、现场作业移动状态,不适合普通“查附近”按钮。调用后会返回一个 ID,必须用 clearWatch 停止。
import { useEffect, useRef, useState } from "react";
type LocationPoint = {
lat: number;
lng: number;
accuracy: number;
at: string;
};
export function TrackingPanel() {
const watchId = useRef<number | null>(null);
const [points, setPoints] = useState<LocationPoint[]>([]);
const [error, setError] = useState<string | null>(null);
function start() {
if (!navigator.geolocation || watchId.current !== null) return;
watchId.current = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
setPoints((current) => [
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracy: Math.round(accuracy),
at: new Date(position.timestamp).toISOString(),
},
...current.slice(0, 9),
]);
},
(err) => setError(`追踪失败:${err.code}`),
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 5000,
},
);
}
function stop() {
if (watchId.current === null) return;
navigator.geolocation.clearWatch(watchId.current);
watchId.current = null;
}
useEffect(() => stop, []);
return (
<section>
<button type="button" onClick={start}>开始追踪</button>
<button type="button" onClick={stop}>停止</button>
{error && <p role="alert">{error}</p>}
<ol>
{points.map((point) => (
<li key={point.at}>
{point.lat}, {point.lng}
{" / "}
{point.accuracy}m
</li>
))}
</ol>
</section>
);
}
持续追踪要有明确的开始、停止和保存策略。不要把“用户打开过页面”变成后台追踪。即使业务需要轨迹,也要先定义保存多久、谁能查看、如何删除。
隐私与日志设计
经纬度不应该作为普通调试信息处理。它可能进入浏览器控制台、错误监控、分析事件、服务器日志、客服截图或录屏工具。更稳妥的做法是只记录权限状态、精度分组和非常粗的位置分组。
type GeoLogInput = {
lat: number;
lng: number;
accuracy: number;
permission: "granted" | "prompt" | "denied" | "unknown";
};
export function toPrivacySafeGeoLog(input: GeoLogInput) {
return {
permission: input.permission,
accuracyBucket:
input.accuracy <= 50 ? "high" :
input.accuracy <= 500 ? "medium" : "low",
latBucket: Number(input.lat.toFixed(2)),
lngBucket: Number(input.lng.toFixed(2)),
};
}
如果只是优化转化,很多事件不需要坐标,例如 manual_search_used、permission_denied、timeout、store_results_shown。让 Claude Code 改日志时,要明确“禁止记录原始 latitude 和 longitude”。
Permissions API 可以读取 granted、prompt、denied 等状态,但不要把它当成万能方案。真正的浏览器权限弹窗仍然发生在调用 getCurrentPosition 或 watchPosition 时。
export async function readGeoPermission() {
if (!("permissions" in navigator)) return "unknown";
try {
const status = await navigator.permissions.query({
name: "geolocation",
});
return status.state;
} catch {
return "unknown";
}
}
失败模式与测试
必须测试 HTTPS。现代浏览器要求安全上下文,普通 HTTP 或被不安全页面嵌套的 iframe 可能直接失败。还要测试 Permissions-Policy,因为跨域 iframe 可能被顶层页面禁止使用 geolocation。
必须测试拒绝权限。拒绝是正常选择,不是异常用户。UI 应该自然切换到手动输入,而不是反复弹窗或显示技术错误。
必须测试超时和位置不可用。室内环境、桌面设备、系统定位关闭、VPN、企业浏览器策略都会触发失败路径。
Chrome DevTools 的 Sensors 面板适合手动检查;Playwright 可以做稳定的自动化测试。
import { expect, test } from "@playwright/test";
test.use({
geolocation: {
latitude: 31.230416,
longitude: 121.473701,
accuracy: 50,
},
permissions: ["geolocation"],
});
test("shows nearby stores from mocked location", async ({ page }) => {
await page.goto("/stores");
await page.getByRole("button", { name: "使用当前位置" }).click();
await expect(page.getByText("已获取当前位置")).toBeVisible();
});
地图服务也要单独测试。Geolocation 成功不代表地图 SDK 成功,也不代表地理编码、路线规划或门店搜索接口成功。API key 限制、配额、账单和服务区域都属于地图层。
安全的 Claude Code 提示词
位置功能的提示词要写清范围、禁止事项和验收标准。不要只说“实现定位功能”。
claude <<'PROMPT'
Implement a beginner-friendly Geolocation feature.
Scope:
- Edit only src/features/location and related tests.
- Do not change billing, analytics, or map provider config.
- Preserve existing API keys and environment variable names.
Requirements:
- Request location only after the user clicks a button.
- Explain why location is needed before the browser prompt.
- Use getCurrentPosition with timeout and maximumAge.
- Add manual postcode/address fallback for denied or timeout cases.
- Do not log raw latitude or longitude.
- Add a Playwright test with mocked geolocation.
- Return a short verification checklist.
PROMPT
团队使用 Claude Code 时,可以用 permission rules 保护 .env、禁止自动 push,并让测试命令保持可执行。
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(git push *)"
],
"allow": [
"Bash(npm test *)",
"Bash(npm run lint)"
]
}
}
如果团队希望把位置功能、地图服务、隐私说明和测试流程整理成可复用规范,可以查看 Claude Code 培训与咨询。带上现有地图供应商、隐私约束和一个真实用户流程,效果会比通用代码片段好得多。
实际验证记录
本文示例在 Chrome 的 localhost 环境下验证过,并使用 DevTools Sensors 设置固定位置和 Location unavailable。Playwright 示例验证了授予 geolocation 权限后的成功路径。上线前还应补充:系统定位关闭、企业策略禁止定位、跨域 iframe、用户拒绝权限、地图服务配额不足、日志中是否出现原始经纬度。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。