导读:本期聚焦于小白龙创作的《微信公众号自定义菜单如何实现点击后弹出地理位置选择器?》,敬请观看详情。当业务需要获取用户当前位置来做附近门店推荐或打卡签到时,让用户手动输入地址体验很差。微信公众号自定义菜单提供了一种原生能力:将菜单类型设为location_select,用户点击后微信客户端会直接弹出地图选择器,选中位置后自动把经纬度推送到开发者服务器。事件推送的Event值为LOCATION,XML中携带Latitude、Longitude和Precision三个字段,不包含EventKey,因此同一公众号下多个位置选择菜单无法通过事件区分。实现流程分两步:先通过接口创建包含location_select类型的菜单,再在服务器回调地址中解析事件XML并提取坐标。本文结合PHP代码演示完整接入过程,并讨论取消操作、重复推送和坐标精度等常见问题。

在微信公众号开发中,获取用户地理位置是一个高频需求。无论是附近门店查询、基于位置的签到,还是同城配送计算距离,都需要拿到用户的经纬度。如果单纯让用户在输入框里填写地址,体验差且坐标不精确。微信公众号自定义菜单提供了一个专门的类型——location_select,用户点击菜单后,微信客户端会直接弹出系统级的地理位置选择器,用户从地图上选一个点,确认后微信服务器就会把该点的经纬度推送给开发者服务器。整个过程不需要额外申请定位权限,也不需要前端调用任何地图SDK,实现成本很低。

微信公众号自定义菜单如何实现点击后弹出地理位置选择器?

本文会从菜单类型的原理讲起,详细说明如何通过官方接口创建带有location_select按钮的自定义菜单,以及服务器端如何接收并解析LOCATION事件。代码示例使用PHP,因为这是公众号开发中使用最广泛的语言,其他语言的开发者可以参照接口流程进行迁移。需要注意,location_select事件推送中没有EventKey字段,这意味着如果创建了多个位置选择菜单,服务器无法直接区分是哪个菜单触发的,实际项目里一般只保留一个此类菜单,或者配合其他交互逻辑来区分。

location_select菜单类型的触发机制

微信公众号自定义菜单支持多种按钮类型,常用的有click(点击推事件)、view(跳转URL)、scancode_push(扫码推事件)、location_select(弹出地理位置选择器)等。其中location_select是专门为获取用户位置设计的类型,用户在微信内点击该菜单后,不会触发网页跳转,也不会直接给服务器发送一个普通点击事件,而是在微信客户端内部打开一个地图选择器页面。用户可以拖动地图、搜索地点,或者直接使用当前定位,然后点击发送。只有用户确认发送位置后,微信服务器才会向开发者配置的服务器地址推送一条事件消息,事件类型Event的值为LOCATION。

与click类型事件不同的是,LOCATION事件的XML消息体中不包含EventKey字段。click事件会带上key值,服务器根据key区分用户点了哪个按钮;而location_select事件只携带用户选择的位置坐标信息,字段包括Latitude(纬度)、Longitude(经度)和Precision(精度,单位为米)。这意味着如果公众号菜单里同时放置了多个location_select按钮,用户点击任意一个,推送的事件结构完全一样,服务器端无法判断具体是哪个菜单触发的。因此在实际开发中,一般不会同时配置多个位置选择菜单,如果需要多个入口,可以先让用户点击一个click菜单,再回复一个包含位置选择链接的消息,或者直接使用微信JS-SDK的getLocation接口。

还需要理解的是,如果用户在位置选择器页面点击了取消,微信客户端不会向服务器发送任何事件,也就是说服务器根本不知道用户曾经打开过选择器。这一点在业务设计时需要提前考虑,比如用户点击菜单后长时间没有收到事件,需要给用户一些提示,引导重新点击。另外,LOCATION事件推送的坐标是基于WGS84标准,而不是国内常用的GCJ-02加密坐标,如果后续要在地图上展示或者计算距离,需要注意坐标系的转换。

通过接口创建自定义菜单

创建自定义菜单需要先获取接口调用凭证access_token,然后调用菜单创建接口。菜单结构是一个JSON对象,每个一级菜单或二级菜单都是一个button对象,button的type字段指定菜单类型。对于location_select类型,type值为location_select,同时必须设置name(菜单标题)和key(菜单标识,虽然事件推送中不返回key,但接口要求必填)。下面是一段PHP代码,演示如何构造菜单JSON并调用创建接口。

<?php
// 获取access_token,实际项目中建议缓存,避免频繁请求
$appid = '你的AppID';
$secret = '你的AppSecret';
$tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
$tokenResult = json_decode(file_get_contents($tokenUrl), true);
$accessToken = $tokenResult['access_token'] ?? '';

if (!$accessToken) {
    echo '获取access_token失败';
    exit;
}

// 菜单结构,这里只配置一个location_select按钮
$menuData = [
    'button' => [
        [
            'name' => '我的位置',
            'type' => 'location_select',
            'key' => 'get_location',
        ],
        // 可以继续添加其他菜单,比如click类型
        [
            'name' => '关于我们',
            'type' => 'click',
            'key' => 'about_us',
        ],
    ],
];

$menuJson = json_encode($menuData, JSON_UNESCAPED_UNICODE);
$createUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token={$accessToken}";

// 使用cURL发送POST请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $createUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $menuJson);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);

echo $response;
echo "\n菜单JSON:\n" . $menuJson;
?>

上述代码中,菜单JSON使用了JSON_UNESCAPED_UNICODE选项,这样中文字符不会转义成unicode编码,方便调试查看。创建成功后,微信返回的JSON中errcode为0。需要特别注意的是,菜单key在同一个公众号下必须唯一,即使location_select事件不含key,接口创建时也不能与其他菜单的key重复,否则会报错。另外,一级菜单最多3个,二级菜单最多5个,超出限制会导致创建失败。

如果使用测试号,也可以在微信公众平台后台的可视化界面直接配置菜单,但接口方式更适合程序化管理、动态更新菜单。通过接口创建菜单后,微信客户端会在用户重新进入公众号会话时刷新菜单,一般几分钟内生效。在开发调试阶段,建议先删除旧菜单再创建新菜单,避免缓存导致测试结果不准确。删除菜单接口为:GET https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN。

接收并解析LOCATION事件

用户点击定位菜单并选择位置后,微信服务器会向公众号配置的服务器地址发送一个POST请求,请求体为XML格式。一个典型的LOCATION事件XML如下所示。

<xml>
  <ToUserName><![CDATA[公众号原始ID]]></ToUserName>
  <FromUserName><![CDATA[用户OpenID]]></FromUserName>
  <CreateTime>1712345678</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[LOCATION]]></Event>
  <Latitude>23.137466</Latitude>
  <Longitude>113.352425</Longitude>
  <Precision>119.385040</Precision>
</xml>

服务器收到请求后,需要先验证签名(如果是明文模式可以只处理事件),然后解析出Event字段,判断是否为LOCATION,再提取经纬度和精度。下面是一段完整的PHP处理代码,包括签名验证、XML解析和回复文本消息。实际部署时,应将代码放在公众号后台配置的服务器URL对应的入口文件中。

<?php
// 服务器地址入口文件,例如 https://yourdomain.com/wechat.php
define('TOKEN', '你公众号后台配置的Token');

// 获取微信服务器发送的数据
$postStr = file_get_contents('php://input');
if (empty($postStr)) {
    echo 'no data';
    exit;
}

// 验证签名
$signature = $_GET['signature'] ?? '';
$timestamp = $_GET['timestamp'] ?? '';
$nonce = $_GET['nonce'] ?? '';
$tmpArr = [TOKEN, $timestamp, $nonce];
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);

if ($signature !== $tmpStr) {
    // 签名验证失败,记录日志后可返回空
    echo '';
    exit;
}

// 解析XML
libxml_disable_entity_loader(true);
$xmlObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($xmlObj === false) {
    echo 'success';
    exit;
}

$msgType = (string)$xmlObj->MsgType;
$event = (string)$xmlObj->Event;
$fromUser = (string)$xmlObj->FromUserName;
$toUser = (string)$xmlObj->ToUserName;

if ($msgType === 'event' && $event === 'LOCATION') {
    $latitude = (float)$xmlObj->Latitude;
    $longitude = (float)$xmlObj->Longitude;
    $precision = (float)$xmlObj->Precision;

    // 在这里处理业务逻辑,比如保存位置、查询附近门店等
    // 这里仅演示回复用户一段文本
    $content = "已收到你的位置:纬度 {$latitude},经度 {$longitude},精度 {$precision} 米";
} else {
    $content = '点击菜单可获取位置信息';
}

// 回复文本消息
$replyXml = "<xml>
<ToUserName><![CDATA[{$fromUser}]]></ToUserName>
<FromUserName><![CDATA[{$toUser}]]></FromUserName>
<CreateTime>" . time() . "</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{$content}]]></Content>
</xml>";

echo $replyXml;
exit;
?>

代码中先验证了签名,保证请求确实来自微信服务器。验证通过后使用simplexml_load_string解析XML,LIBXML_NOCDATA参数可以自动将CDATA内容转换为字符串。判断MsgType为event且Event为LOCATION后,提取Latitude、Longitude、Precision三个字段。回复的XML中要特别注意字段顺序,并且ToUserName要写用户的OpenID,FromUserName写公众号的原始ID,两者不要写反。回复的内容需要包在CDATA中,避免特殊字符导致XML解析错误。

关于Precision字段,它表示定位精度,单位是米。比如Precision为119.385040,意味着定位误差大约在119米左右。这个值越小说明定位越准确,实际业务中可以根据精度做过滤,例如精度大于500米的坐标可以提示用户重新选择。另外,坐标是WGS84标准,如果后续使用高德或腾讯地图展示,需要转换为GCJ-02坐标,转换算法网上有现成实现,或者直接使用地图服务商提供的坐标转换API。由于location_select事件不包含EventKey,如果业务上必须区分多个位置菜单入口,可以考虑在用户点击前先让其点击一个click菜单,服务器回复一个提示,引导用户点击特定的位置菜单,或者干脆只保留一个位置入口。

开发中的常见问题与调试建议

第一个常见问题是用户取消选择器后收不到任何事件。前面提到,微信不会推送取消操作,服务器无法感知。为了优化体验,可以在用户点击菜单后,服务器暂时无法得知,所以更好的做法是在菜单名称上做提示,例如叫“发送我的位置”,让用户明确知道点击后要选择一个点并发送。同时,如果业务对位置获取成功率要求很高,可以结合客服消息或者模板消息,在用户长时间未操作时主动提醒,但实现起来较为复杂。另一个思路是放弃location_select,改用JS-SDK的getLocation接口,由网页自己处理取消和超时逻辑,这样可控性更强,但需要用户授权网页定位权限,且必须使用认证服务号。

第二个问题是测试环境的配置。公众号开发需要公网可访问的服务器地址,本地调试可以使用内网穿透工具。配置服务器URL时,Token需要与代码中的TOKEN常量保持一致,EncodingAESKey如果使用明文模式可以不填。验证通过后,才能正常接收事件推送。调试过程中,可以在入口文件开头将$postStr写入日志文件,方便查看微信实际推送的XML内容。日志文件路径需要注意服务器写权限,比如在Linux环境下可以写到/tmp/wechat.log。

第三个问题是坐标数据的使用。拿到经纬度后,通常会保存到数据库,后续做附近推荐或者距离计算。如果使用的是MySQL,可以新建一张用户位置表,字段包含openid、latitude、longitude、precision、create_time等。计算两个坐标点之间的距离可以使用Haversine公式,但注意数据库中的经纬度要按照浮点数存储,不要存成字符串,以免排序和计算出错。对于高并发场景,频繁写入位置信息可能造成数据库压力,可以考虑先写入Redis,再异步批量落库。此外,用户可能多次点击定位菜单,每次都会收到新坐标,业务上需要决定是以最新一次为准,还是记录历史轨迹,这些都需要根据产品需求提前设计好。

最后要强调的是,location_select菜单虽然方便,但只能获取用户主动选择的位置,并非实时GPS定位。用户可以选择地图上任意一点,甚至不是自己所在的位置,所以不要将这种数据用于需要强身份验证的场景,比如基于位置的考勤打卡,用户可以作弊。如果需要更高可信度的位置,可以结合手机号验证或拍照等方式。但对于大多数基于位置的推荐和展示类业务,location_select已经能够满足需求。

微信公众号自定义菜单地理位置选择器修改时间:2026-10-04 19:27:26

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1004/65683.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。