导读:本期聚焦于大象创作的《微信公众号自定义菜单事件怎么实现?点击菜单弹出积分商城并展示用户积分》,敬请观看详情。公众号自定义菜单点击后如何跳转到带用户身份的积分商城页面?核心在于菜单click事件与view事件的区别、OAuth2.0网页授权获取openid,以及服务端查询积分后渲染页面。本文完整讲解自定义菜单的创建方式、事件推送的数据结构、access_token获取、授权回调链路的搭建,并提供可直接运行的PHP与Node.js代码示例,同时分析免登录、积分安全校验和常见报错的处理办法,帮你把菜单点击到积分展示的整条链路跑通。

在微信公众号运营场景里,积分商城是最常见的活跃手段之一。用户点击底部菜单里的「积分商城」,页面打开后直接显示自己的当前积分和可兑换商品,这种体验背后的实现涉及三件事:自定义菜单的创建、菜单事件的处理、以及通过网页授权识别用户身份后查询积分。这篇文章把整条链路拆开来讲,并附上可以直接参考的代码。

微信公众号自定义菜单事件怎么实现?点击菜单弹出积分商城并展示用户积分

一、自定义菜单的两种类型:click与view的选择

公众号自定义菜单的按钮类型里,和积分商城相关的主要是两种:click和view。click类型的按钮被点击后,微信会以事件消息的形式推送到开发者配置的服务器地址,你的服务端收到XML或JSON报文后可以自由决定做什么,比如回复一条图文消息、下发一个跳转链接。view类型的按钮则直接跳转到你指定的URL,不经过事件推送。

对于积分商城这种H5页面,推荐用view类型,URL指向一个带网页授权的中间页。如果你的业务还需要在点击时记录行为、做埋点或下发卡券,可以再加一个click按钮先接收事件再引导跳转。两种方式各有取舍:view实现简单、跳转快;click灵活但多一次服务端交互。

创建菜单需要调用微信的接口,地址是https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN。请求体示例:

{
  "button": [
    {
      "type": "view",
      "name": "积分商城",
      "url": "https://yourdomain.com/mall/auth"
    },
    {
      "type": "click",
      "name": "我的积分",
      "key": "MY_POINTS"
    }
  ]
}

这里的url建议不要直接写商城最终页面,而是写一个授权中转地址。因为要在商城里显示用户积分,就必须知道用户是谁,这就涉及下一节的网页授权。

二、网页授权获取用户身份:openid是积分查询的钥匙

识别用户身份靠的是微信的OAuth2.0网页授权机制。流程是:用户点击菜单跳转到你的中转页,中转页重定向到微信授权地址,微信带上code回调你的redirect_uri,服务端用code换取用户信息。如果公众号是已认证的服务号,使用snsapi_base静默授权即可拿到openid,用户全程无感知,体验最好。

授权地址的格式为:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect。注意redirect_uri需要urlencode处理,state可以带上回跳目标参数。服务端用code换取openid的代码示例如下:

<?php
// 中转页:引导用户进入网页授权
$appid = '你的APPID';
$redirect = urlencode('https://yourdomain.com/mall/callback');

header('Location: https://open.weixin.qq.com/connect/oauth2/authorize?' .
    'appid=' . $appid .
    '&redirect_uri=' . $redirect .
    '&response_type=code' .
    '&scope=snsapi_base' .
    '&state=mall' .
    '#wechat_redirect');
exit;

// callback.php:用code换取openid
function getOpenid($appid, $secret, $code) {
    $url = "https://api.weixin.qq.com/sns/oauth2/access_token?" .
           "appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code";
    $res = json_decode(file_get_contents($url), true);
    return $res['openid'] ?? null;
}

有一个容易被忽略的坑:code只能使用一次,且有效期为5分钟。用户频繁刷新回调页时会报40163错误(code been used),解决办法是在回调成功后把openid写入session,后续请求直接走session,不再重复授权。另外redirect_uri的域名必须与公众号后台「网页授权域名」配置一致,且需要放置MP_verify开头的校验文件到服务器根目录。

三、服务端查询积分并渲染商城页面

拿到openid后,积分查询就是纯业务逻辑了。你的用户体系里应该有一张绑定表,把openid和系统内的user_id关联起来。查询积分时按openid找到user_id,再从积分表汇总。下面是一个Node.js加Express的完整示例:

const express = require('express');
const axios = require('axios');
const app = express();

app.get('/mall/callback', async (req, res) => {
  const { code } = req.query;
  // 用code换取openid
  const tokenRes = await axios.get('https://api.weixin.qq.com/sns/oauth2/access_token', {
    params: {
      appid: process.env.WX_APPID,
      secret: process.env.WX_SECRET,
      code,
      grant_type: 'authorization_code'
    }
  });
  const openid = tokenRes.data.openid;
  // 根据openid查询用户积分
  const user = await db.query('SELECT user_id FROM user_bind WHERE openid = ?', [openid]);
  if (!user.length) {
    return res.render('mall', { openid, points: 0, isBound: false });
  }
  const points = await db.query(
    'SELECT COALESCE(SUM(change_value), 0) AS total FROM points_log WHERE user_id = ?',
    [user[0].user_id]
  );
  res.render('mall', { openid, points: points[0].total, isBound: true });
});

积分商城页面在渲染时,建议把积分值直接输出到HTML里而不是靠前端异步请求,这样首屏就能展示积分,用户感知更快。如果商城是前后端分离的单页应用,则可以在前端先展示骨架屏,再用openid对应的token去调积分接口。注意接口层一定要做鉴权,不能只凭前端传来的openid就返回积分——openid虽然是用户标识,但它不是秘密,被伪造后任何人都能查别人的积分。正确做法是授权成功后签发一个短有效期的JWT,前端携带token调接口,服务端验证签名后再查询。

四、click事件推送的处理与常见问题

如果你选择用click类型按钮做「我的积分」这类快捷查询,就需要处理微信推送的事件消息。微信会把XML报文POST到你配置的服务器URL,其中Event字段为CLICK,EventKey是你创建菜单时设置的key。服务端解析出FromUserName(即用户openid)后查积分,再以被动回复消息的形式返回。Node.js里的处理示例:

app.post('/wx/message', express.text({ type: 'text/xml' }), (req, res) => {
  parseXML(req.body).then(async (msg) => {
    if (msg.Event === 'CLICK' && msg.EventKey === 'MY_POINTS') {
      const openid = msg.FromUserName;
      const points = await getPointsByOpenid(openid);
      // 被动回复文本消息
      res.send(buildTextReply(msg.ToUserName, openid,
        '您当前积分:' + points + ',点击菜单「积分商城」可去兑换好礼'));
    } else {
      res.send('success');
    }
  });
});

最后梳理几个高频报错。菜单创建返回40019说明button的key冲突;返回48001说明接口未授权,自定义菜单接口只有认证后的公众号才能用,个人订阅号不支持。获取access_token时出现40125是AppSecret错误,建议把access_token集中存到Redis并设置7000秒左右的过期时间,避免频繁刷新触发限流。token与普通access_token是两个不同的东西,别混用。整套链路跑通后,从用户点击菜单到看到自己的积分,整个体验可以控制在一秒以内。

微信公众号自定义菜单积分商城网页授权修改时间:2026-09-10 04:24:33

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