微信公众号扫码登录开发实录
- 前言
- 一、服务器配置
- 1.微信公众号配置
- 2.本地服务器验证程序
- 二、生成登录二维码
- 1.生成微信登录二维码
- 2.封装成便于刷新的函数
- 三、扫码验证和交互
- 四、模版消息设置
- 五、开发中遇见的问题
- 1.该公众号提供的服务出现故障,请稍后再试?
- 2.微信第三方平台的token和jssdk的token是否一致?
- 3.微信公众号消息模版调用的是哪个token?
- 4.微信公众号可以批量推送消息模版吗
- 5.php如何配置微信公众号的服务器配置
- 6.扫描二维码后,php与微信公众号之间的推送和接收代码是怎么样的?
- 7.扫描后,无法获取file_get_contents("php://input")
- 8.HTTP_RAW_POST_DATA的使用方法是什么
- 9.微信首次关注,未触发事件如何处理?
前言
微信公众号扫码登录在安全性、用户体验和数据收集方面优势明显,是目前许多应用程序采用的登录方式之一。
-
无需记住账号密码:用户无需输入和记住账号和密码,只需使用微信扫描二维码即可快速登录。
-
安全性高:微信采用 OAuth2.0 协议,将用户信息传递给第三方应用程序,而不会向第三方应用程序公开用户的微信账号和密码,有效保护用户隐私和安全。
-
用户体验好:扫码登录可以在移动设备上快速完成登录操作,无需输入许多文字,提高用户体验和用户参与度。
-
便于推送内容:通过微信公众号扫码登录,第三方应用程序可以获得用户的微信头像、昵称和 openid 等信息,这些信息对于精准推送内容非常有帮助。
-
方便分享内容:在扫码登录后,用户可以通过微信分享按钮将文章等内容分享给朋友,实现了“口碑”式传播。
一、服务器配置
1.微信公众号配置
切记,配置好需要点击“启用”。
2.本地服务器验证程序
// 微信token认证
$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];
$echostr = $_GET["echostr"];
// 你在微信公众号后台的设置的Token
$token = "3cab7ce41****ca24";// 1)将token、timestamp、nonce三个参数进行字典序排序
$tmpArr = array($nonce, $token, $timestamp);
sort($tmpArr, SORT_STRING);// 2)将三个参数字符串拼接成一个字符串进行sha1加密
$str = implode($tmpArr);
$sign = sha1($str);// 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if ($sign == $signature) {
echo $echostr;
}
二、生成登录二维码
1.生成微信登录二维码
生成微信登录二维码的代码需要使用微信开放平台提供的 API,请参考以下步骤:
- 获取 access_token
在使用微信公众号提供的 API 时,需要先获得一个 access_token。可以通过以下 URL 地址获取:
public function getAccessToken()
{
// access_token 应该全局存储与更新,以下代码以写入到文件中做示例
$data = json_decode($this->get_php_file("libs/wx/access_token.php"));
if ($data->expire_time < time()) {
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" . $this->appId . "&secret=" . $this->appSecret;
$res = json_decode($this->httpGet($url));
$access_token = $res->access_token;
if ($access_token) {
$data->expire_time = time() + 7200;
$data->access_token = $access_token;
$this->set_php_file("libs/wx/access_token.php", json_encode($data));
}
} else {
$access_token = $data->access_token;
}
return $access_token;
}
其中 APPID 和 APPSECRET 分别是在微信公众号创建应用后获取的应用 ID 和应用密钥。将该 URL 地址进行 url encode,然后发送 GET 请求即可获取 access_token。
- 生成登录二维码
将获取到的 Ticket 通过以下 URL 地址生成登录二维码:
$qrcode_url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $this->getAccessToken();
public function getQrcodeTicket()
{
$qrcode_url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $this->getAccessToken();
$post_data = array();
$post_data['expire_seconds'] = 1200; //有效时间
$post_data['action_name'] = 'QR_SCENE';
$post_data['action_info']['scene']['scene_id'] = $this->make_uid(); //传参二维码主键id,微信端可获取
$json = $this->httpPost($qrcode_url, json_encode($post_data));
if (!$json['errcode']) {
$data['url'] = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' . urlencode($json['ticket']);
$data['uid'] = $post_data['action_info']['scene']['scene_id'];
$data['ticket'] = $json['ticket'];
return json_encode($data);
} else {
//删除缓存文件 By WoodCutter 临时措施 2023-06-24;
if ($json['errcode'] == "41001") {
$file1 = 'libs/wx/access_token.php';
$file2 = 'libs/wx/jsapi_ticket.php';
if (file_exists($file1)) {
unlink($file1);
unlink($file2);
}
}
die('发生错误:错误代码 ' . $json['errcode'] . ',微信返回错误信息:' . $json['errmsg']);
}
}
2.封装成便于刷新的函数
- 封装函数
//生成二维码;
case "makeQrcode";
require_once "libs/wx/jssdk.php";
$CONF = require_once "conf/config.php";
$jssdk = new JSSDK($CONF['wx_appId'], $CONF['wx_AppSecret']);
$code_img = json_decode($jssdk->getQrcodeTicket(), true);
$res['ticket'] = $code_img['ticket'];
$res['scene_id'] = $code_img['uid'];
$res['url'] = $code_img['url'];
$res['code'] = 1;
$res['msg'] = "成功生成登录二维码";
die(json_encode_lockdata($res));
break;
- 生成二维码的同时,将ticket、scene_id、url同步传递到前端。
public function getQrcodeTicket()
{
$qrcode_url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $this->getAccessToken();
$post_data = array();
$post_data['expire_seconds'] = 120; //有效时间
$post_data['action_name'] = 'QR_SCENE';
$post_data['action_info']['scene']['scene_id'] = $this->make_uid(); //传参二维码主键id,微信端可获取
$json = $this->httpPost($qrcode_url, json_encode($post_data));
if (!$json['errcode']) {
$data['url'] = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' . urlencode($json['ticket']);
$data['uid'] = $post_data['action_info']['scene']['scene_id'];
$data['ticket'] = $json['ticket'];
return json_encode($data);
} else {
die('发生错误:错误代码 ' . $json['errcode'] . ',微信返回错误信息:' . $json['errmsg']);
}
}
- 前端二维码容器
<div class="photo">
<img src="" width="180" height="180" id="qrcode">
<p>关注微信公众号(点击刷新二维码)</p>
</div>
- 前端调用
//请求登录二维码
getWxQRcode();
$("#qrcode").click(function () {
getWxQRcode();
})
//获取微信登录二维码
function getWxQRcode() {
$.getJSON("?m=Login&a=loginDeal&act=makeQrcode", function (data) {
$("#qrcode").attr("src", data.url);
ticket = data.ticket;
scene_id = data.scene_id;
});
}
三、扫码验证和交互
- 扫码,微信服务器推送信息
//$postStr = file_get_contents("php://input");//php版本>7.0使用
$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
if (!empty($postStr)) {
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$eventType = strtolower($postObj->Event);
$scene_id = trim($postObj->EventKey);
$replyMsg = "微信扫码登录";
$fromUsername = trim($postObj->ToUserName);
$toUsername = trim($postObj->FromUserName);
$replyTime = time(); /* 业务逻辑
* 扫码后,将相关数据保存在服务器,以便扫码比对
* $upload_dir,票据保存目录
* $ticket,票据名称
* $scene_id,场景值ID
* */
$upload_dir = 'upload/ticket/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
$ticket = $postObj->Ticket;
$data = ['openid' => $toUsername, 'scene_id' => $scene_id];
$content = json_encode($data);
$fp = fopen('./upload/ticket/' . $scene_id . "_" . $ticket . ".json", "w");
fwrite($fp, $content);
fclose($fp); //已关注
if ($eventType == 'scan') {
//读取数据库,判断$toUsername是否绑定
/* 已绑定,执行登陆
* 是否绑定手机号
* */
//文本消息
// . "-" . $eventType . "-" . $toUsername
$resultStr = $jssdk->sendText($toUsername, $fromUsername, $replyMsg);// . "-" . $scene_id . "-" . json_encode($postObj)
echo $resultStr; /* 模版消息
* 必须填填写,否则无推送(模版消息,是return;文本消息是echo)
* 测试openid:oe2dH62ZgphwmJRGvAVTlCJjWTN4
* */
/* $resultStr = $jssdk->sendWXMsg(trim($toUsername), $CONF['sys_name'], 'scan');
return $resultStr;*/
} //首次关注
if ($eventType == 'subscribe') {
//文本消息
$resultStr = $jssdk->sendtext($toUsername, $fromUsername, '感谢关注' . $CONF['sys_name']);
echo $resultStr;
/*$resultStr = $jssdk->sendWXMsg(trim($toUsername), $CONF['sys_name'], 'subscribe');
return $resultStr;*/
}
- 前端轮询
//验证扫码状态
function wxLogin() {
$.getJSON("?m=Login&a=loginDeal&act=wx_login", {ticket: ticket, scene_id: scene_id}, function (res) {
//console.log(res);
//已绑定用户
if (res.code == 1) {
layer.msg(res.msg, {icon: 1, time: 2000}, function () {
location.reload();
});
}
//未绑定用户
if (res.code == 2) {
layer.msg(res.msg, {icon: 2, time: 2000}, function () {
location.href = '?m=User&a=userBind';
});
}
}
);
} //轮询
var timer = setInterval(function () {
wxLogin();
}, 2000);
- 后端验证扫码
//判断二维码是否扫码
case "wx_login";
$ticket = get_param("ticket");
$scene_id = get_param("scene_id");
$file = "upload/ticket/" . $scene_id . "_" . $ticket . ".json";
@$login = file_get_contents($file);
if ($login) {
$openid = json_decode($login)->openid;
$_SESSION['openid'] = $openid;
//发送登录模版消息
require_once "libs/wx/jssdk.php";
$jssdk = new JSSDK($CONF['wx_appId'], $CONF['wx_AppSecret']);
$resultStr = $jssdk->sendWXMsg(trim($openid), $CONF['sys_name'], 'scan');
//return $resultStr; //验证码自动销毁;
session_destroy(); //删除临时文件
if (file_exists($file)) {
unlink($file);
}
//$res['openid'] = $openid;
$res['code'] = 1;
$res['msg'] = "登录成功";
die(json_encode_lockdata($res));
} else {
$res['code'] = 0;
$res['msg'] = "无法获取票据";
die(json_encode_lockdata($res));
}
break;
四、模版消息设置
//发送模版消息
public function sendWXMsg($openid, $sysTitle, $temp_type)
{
$json_template = $this->jsonTemplate($openid, $sysTitle, $temp_type);
$url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" . $this->getAccessToken();
$res = json_decode($this->curl_post($url, urldecode(trim($json_template))), true);
if ($res['errcode'] == 0) {
return '发送成功';
} else {
return $res['errmsg'];
}
} /**
* 模板消息json格式化
* $openid,用户openid;
* $template_id,在公众号下配置的模板id
* $temp_type,消息类型
*/
private function jsonTemplate($openid, $sysTitle, $temp_type)
{
$skipUrl = 'http://xiaoe.***.cn';//点击模板消息会跳转的链接
switch ($temp_type) {
case "scan"://扫描登录
$template_id = "HXdRtFldq88I-z0aIgwv-9_4FXtQFbfYqR83kM1-29s";
$template = array(
"touser" => $openid,
"template_id" => trim($template_id),
"url" => $skipUrl,
"data" => array(
"thing4" => array("value" => urlencode($sysTitle)),
"time3" => array("value" => urlencode(date("Y-m-d H:i:s"))),
"thing10" => array("value" => urlencode("微信扫码登录")),
)
); //模板消息
return json_encode($template);
break; case "subscribe"://首次关注
$template_id = "HXdRtFldq88I-z0aIgwv-9_4FXtQFbfYqR83kM1-29s";
$template = array(
"touser" => $openid,
"template_id" => trim($template_id),
"url" => $skipUrl,
"data" => array(
"thing4" => array("value" => urlencode($sysTitle)),
"time3" => array("value" => urlencode(date("Y-m-d H:i:s"))),
"thing10" => array("value" => urlencode("微信扫码登录(首次关注)")),
)
); //模板消息
return json_encode($template);
break; case "order"://产品订购
$template_id = "RkCMp_dH7F4A3lOO471v9XdAXvmO_7cY21cfCnY6u-3o";
break;
default:
echo "";
}
}
五、开发中遇见的问题
1.该公众号提供的服务出现故障,请稍后再试?
如果正在发送消息模板,会出现消息模板格式错误的情况,导致无法正常发送消息。如果模板格式存在问题,微信公众号服务器会返回相应的错误提示,例如“模板格式错误”、“内容过长”等错误信息。
因此,在使用微信公众号发送消息模板时,应确保模板格式正确、内容符合要求。建议按照微信公众号官方文档中提供的模板格式规范进行开发,并在发送前进行模板格式和内容的检查工作,以避免该问题的发生。
- $toUsername,使用trim()过滤空格,防止发送信息错误;
- 返回值必须要返回一个字符串,否则会导致扫描后公众号提示“该公众号提供的服务出现故障,请稍后再试”。
$resultStr = $jssdk->sendText($toUsername, $fromUsername, $replyMsg);// . "-" . $scene_id . "-" . json_encode($postObj)
echo $resultStr;
2.微信第三方平台的token和jssdk的token是否一致?
微信第三方平台的token和jssdk的token是不同的。
微信第三方平台的token是用于与微信服务器进行交互的身份标识,用于验证第三方平台身份和加解密票据等。 而jssdk的token是用于调用微信JS-SDK时进行验证的票据,通过对微信公众号进行认证后可以获取。它主要用于获取用户信息、分享接口、支付接口等功能的调用。
虽然两者都是token,但由于用途不同,因此它们的生成方式及验证流程也不一样。
3.微信公众号消息模版调用的是哪个token?
微信公众号消息模板调用的是微信公众号的access_token。
access_token是获取微信公众号 API 调用凭证(Token)的接口,每个公众号都有独立的access_token。在调用公众号接口时,需要携带access_token参数,以验证公众号身份。
调用公众号模板消息接口时,需要先获取到access_token后才能进行调用。access_token的获取方式有两种:一种是通过公众号网页授权获取,另一种是通过公众号开发者中心的API token获取。具体获取方式可以参考微信公众号开发文档。
4.微信公众号可以批量推送消息模版吗
微信公众号支持批量发送消息模板。具体实现方式是,开发者可以使用微信公众平台接口向指定OpenID群发模板消息,每次调用最多可以发送给10000个用户。调用接口前需要先获取到公众号的access_token。
调用接口的方法如下:
- 构造请求数据包,包括模板内容和接收用户的openid列表。
- 将数据包格式化为json字符串。
- 调用模板消息发送接口,将消息发送给微信服务器。
- 微信服务器接收到消息后,将模板消息发送给指定OpenID的用户。
需要注意的是,微信规定模板消息的发送频率不能超过每个用户每天1条,且对于相同模板消息,30天内只能发送一次,否则将会被认为是垃圾邮件,会对公众号造成不利影响。
5.php如何配置微信公众号的服务器配置
在PHP中,配置微信公众号的服务器配置需要做以下几件事情:
- 在公众号后台配置服务器地址和Token,务必保证服务器地址与Token填写无误;
- 在自己的服务器端编写代码,对微信服务器发送的请求进行验证和处理;
- 返回特定格式的消息给微信服务器,告诉微信服务器处理结果。
下面是具体操作步骤:
- 在公众号后台配置服务器地址和Token
在公众号后台->开发->基本配置页面填写服务器配置项,将服务器地址与Token填写正确,其中Token是自定义的密钥,可自行设置。
- 编写代码对微信服务器发送的请求进行验证和处理
在自己的服务器端,为事件接口URL配置一个处理请求的入口,并根据微信服务器发送的请求做出相应的响应。具体实现步骤如下:
- 验证请求是否合法,验证请求签名是否正确;
- 通过文件方式或者Redis等缓存技术,存储access_token等参数,保证服务器可以快速响应;
- 处理各种事件、消息等请求,并在消息处理后,按照微信服务器要求,返回特定格式的消息给微信服务器;
- 返回特定格式的消息给微信服务器
处理完请求后,需要将结果按照微信服务器要求,返回特定格式的消息给微信服务器,告诉微信服务器处理结果,否则会导致请求失败。
在向微信服务器发送消息时,需要将响应消息格式化为XML字符串,并按照微信服务器要求,包装成特定的格式。
以上是关于如何在PHP中配置微信公众号的服务器配置的简单介绍,细节和具体代码实现请参考微信公众号官方文档。
6.扫描二维码后,php与微信公众号之间的推送和接收代码是怎么样的?
在 PHP 中,实现扫描二维码后与微信公众号之间的推送和接收可以分为以下几个步骤:
- 配置开发者中心服务器地址和 Token。
需要在微信开发者中心中开启接口配置,填写服务器 URL 和 Token 信息,这些信息将在后续的接口验证中用到。
- 接收和验证微信发送的请求。
当有用户通过扫描二维码进入公众号时,微信会把用户的请求发送到配置的服务器地址中,开发者需要编写代码,接受并验证微信服务器发送的请求的合法性。
- 处理用户请求。
当开发者成功接收并验证了微信发送的请求的合法性后,需要根据业务逻辑对请求进行处理,可以根据不同的场景,进行相关操作,比如数据库操作、数据查询等。
- 回复用户消息。
根据处理结果,开发者需要对微信返回特定结构的消息,告诉微信服务器处理结果并让公众号给用户发送相应的消息,返回的消息格式也需要遵守微信的规范,即按照微信服务器要求组织消息内容,将响应消息内容格式化成XML字符串。
下面是一个简单的接收和处理微信二维码扫描事件的 PHP 代码示例:
// 验证请求签名
$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];
$token = "your_token";
$tmpArr = array($token, $timestamp, $nonce);
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);
if ($signature != $tmpStr) {
die("signature error");
}// 处理请求
$postStr = file_get_contents("php://input");
if (!empty($postStr)) {
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$eventType = $postObj->Event;
if ($eventType == 'SCAN') {
// 处理扫描事件
// 通过扫描事件里的场景值scene_id做进一步的业务逻辑
$sceneId = $postObj->EventKey; // 回复消息,告知用户已经处理了请求
$replyMsg = "您的扫描请求已经处理成功";
$fromUsername = $postObj->ToUserName;
$toUsername = $postObj->FromUserName;
$replyTime = time();
$textTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[%s]]></MsgType>
<Content><![CDATA[%s]]></Content>
<FuncFlag>0</FuncFlag>
</xml>";
$msgType = "text";
$resultStr = sprintf($textTpl, $toUsername, $fromUsername, $replyTime, $msgType, $replyMsg);
echo $resultStr;
}
}
7.扫描后,无法获取file_get_contents(“php://input”)
如果在扫描二维码后使用 file_get_contents("php://input")
无法获取POST过来的数据,一般有以下几种可能原因:
- 服务器未开启
always_populate_raw_post_data
选项
PHP.ini 中配置项 always_populate_raw_post_data
表示是否在初始化时自动填充 $HTTP_RAW_POST_DATA
变量,而 $HTTP_RAW_POST_DATA
变量包含了 POST 过来的原始数据。该选项默认为 0(即关闭状态),如果需要通过 file_get_contents("php://input")
获取 POST 数据,则需要将其设置为 always_populate_raw_post_data = -1
,然后重新启动服务器。
- 请求参数被禁用
在 PHP 中,有一些安全选项可以禁用 $_REQUEST
、$_GET
和 $_POST
等全局变量,可以检查 PHP.ini 中的以下选项是否置为 off
:
variables_order
disable_functions
disable_classes
- 其他原因
如果上述原因都检查过了还是无法获取 POST 数据,可以尝试换一个新的 PHP 版本,也可以考虑使用其他的方法获取 POST 数据,比如使用 php://stdin
、HTTP_RAW_POST_DATA
、stream_get_contents()
等。
需要注意的是,在微信开发中,如果使用了微信官方提供的 SDK 来接收和处理微信的 POST 请求,一般不需要使用 file_get_contents("php://input")
来获取 POST 数据,可以直接通过 SDK 提供的方法来处理请求。
8.HTTP_RAW_POST_DATA的使用方法是什么
在不开启 always_populate_raw_post_data
或者 auto_globals_jit
配置时,$_POST
和 $_FILES
超全局变量中不会保存 POST 过来的原始数据,此时可以使用 $HTTP_RAW_POST_DATA
变量来获取原始 POST 数据。
$HTTP_RAW_POST_DATA
是一个全局变量,它包含了 POST 数据的原始内容。要使用它来获取 POST 数据,可以按照以下步骤操作:
- 设置
always_populate_raw_post_data=-1
在 PHP.ini 中设置 always_populate_raw_post_data=-1
,然后重新启动服务器,确保 $HTTP_RAW_POST_DATA
变量可以正常获取。
- 使用
$HTTP_RAW_POST_DATA
if (isset($HTTP_RAW_POST_DATA)) {
$postRequestData = $HTTP_RAW_POST_DATA;
} else {
$postRequestData = file_get_contents("php://input");
}
在上述代码中,首先检查 $HTTP_RAW_POST_DATA
是否存在,如果存在,则将 POST 数据的原始内容赋值给 $postRequestData
变量,否则,使用 file_get_contents("php://input")
函数从标准输入流中读取原始数据。
需要注意的是,$HTTP_RAW_POST_DATA
变量在 PHP 版本 5.6.0 之后被废弃,并且在 PHP 版本 7.2.0 中被移除,建议使用 php://stdin
或 php://input
来获取 POST 数据。
9.微信首次关注,未触发事件如何处理?
如果用户在未关注公众号的情况下,通过扫描带场景值二维码(或其他途径)进行关注,那么并不会收到关注事件,微信服务器只会默认推送一次文本类型的消息,公众号开发者需要对这种情况进行特殊处理。
具体而言,对于首次关注时未触发关注事件,可以使用替代方案来实现处理,例如在公众号的自动回复中,设置一条欢迎语并放置在第一位,让用户在关注后第一时间接收到并引导用户进行相关操作。
同时,在处理此类情况时,需要注意以下几点:
- 判断用户是否关注过公众号
可以通过在公众号中记录用户的关注状态,来判断用户是否关注过公众号。如果用户已经关注过公众号,则可以正常地处理关注事件。
- 限制重复关注
为避免用户重复关注公众号,可以在处理关注事件时,判断用户的关注状态,只有在用户之前没有关注过公众号的情况下,才进行相应的业务操作。
- 考虑用户体验
处理首次关注时未触发事件的情况,需要考虑到用户的使用体验。在向用户发送欢迎语时,应该具有一定的个性化,并引导用户完成与公众号的互动操作,例如进行菜单的点击、发送关键词等。
需要注意的是,由于微信公众号平台不断升级优化,某些问题可能在后续的版本中得到解决。因此,在出现问题时,开发者可以参考微信公众平台文档中的最新说明,在此基础上进行相应的处理操作。
@漏刻有时
发表评论 取消回复