php、thinkphp 微信公众号扫码登录、绑定
文章目录
- 环境
- 一、绑定
- 二、登录
- 总结
环境
Linux 、Nginx 、PHP 、Mysql、Thinkphp
前提
1、给你的用户表/管理员表 增加一个字段 open_id 字符串类型 长度例如 50 ;
2、建一个微信登录扫码表
domain_id 是我的业务代码ID 可以去除
CREATE TABLE `tp_admin_wechat_login_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '类型(1登录2绑定)',
`ticket` varchar(128) NOT NULL DEFAULT '' COMMENT '二维码票据',
`admin_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
`domain_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '场所ID',
`open_id` varchar(50) NOT NULL DEFAULT '' COMMENT 'open_id',
`is_login` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否登录/绑定(0否1是)',
`create_time` bigint(16) NOT NULL DEFAULT '0' COMMENT '创建时间',
`update_time` bigint(16) unsigned DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='后台微信登录表';
开始
一、绑定
1.微信操作类
代码如下:
我去除掉了一些无关的代码 然后获取accessToken那里用了缓存 注意替换或删除 然后需要配置APPID和SECRET
<?php
namespace app\common\library;
use think\Cache;
use think\Controller;
use think\Db;
use think\Log;
/**
* 微信操作类
*/
class WxFunction extends Controller
{
protected $APPID = '';
protected $APPSECRET = '';
protected $TOKENCACHENAME = '';
public function _initialize()
{
$this->APPID = config('wxpay.APPID');
$this->APPSECRET = config('wxpay.APPSECRET');
}
public function curl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
/**
* @return string
*得到普通的accesstoken
*/
public function getGeneralAccessToken()
{
$this->APPID = config('wxpay.APPID');
$this->APPSECRET = config('wxpay.APPSECRET');
$this->TOKENCACHENAME = 'accessToken';
return Cache::remember($this->TOKENCACHENAME, function () {
try {
$get_token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $this->APPID . '&secret=' . $this->APPSECRET;
$data = $this->curl($get_token_url);
$json_obj = json_decode($data, true);
if ($this->TOKENCACHENAME == 'accessToken') {
return $json_obj["access_token"];
} else {
return [
'accessToken' => $json_obj["access_token"],
'expirationTime' => time() + 3600,
];
}
} catch (\Exception $e) {
// 记录报错 或 抛出异常
}
}, 3600);
}
/**
* @param $url
* @param $json
* @return mixed
* 使用curl方法发送json数据
*/
public function postData($url, $json)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
/**
* @param $access_token
* @param $action_name
* @param $scene_id
* @return string 微信返回的全部信息
*/
public function getQrcodeUrl($scene_id, $action_name = 'QR_LIMIT_STR_SCENE')
{
$access_token = $this->getGeneralAccessToken();
$data = [
"action_name" => $action_name,
"action_info" => [
"scene" => [
"scene_str" => $scene_id,
],
],
];
$url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" . $access_token;
$res = $this->postData($url, json_encode($data));
return $res;
}
}
2.微信回调函数
代码如下:
关注和未关注走的是两个方法 需注意的是 微信访问的时候 需要让他能访问到 例如:不需要登录
<?php
namespace app\api\controller;
use app\admin\model\Admin;
class Wxserver
{
public function index()
{
$xml = file_get_contents('php://input', 'r');
//转成php数组 禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
if ($data['MsgType'] == 'event') {
switch ($data['Event']) {
case 'subscribe':
return $this->subscribe($data);
break;
case 'SCAN':
return $this->scan($data);
break;
default:
# code...
break;
}
} else {
die(' ');
}
}
//未关注用户
protected function subscribe($data)
{
$data['EventKey'] = str_replace('qrscene_', '', $data['EventKey']);
//如果不带参数关注
if ($data['EventKey'] == []) {
} //如果带参数关注
else {
$EventKey = json_decode($data['EventKey'], true);
switch ($EventKey['type']) {
case 2:
Admin::bind_wechat_start($EventKey['admin_id'], $data['FromUserName'], $EventKey['domain_id'], $data['Ticket']);
break;
case 3:
Admin::wechat_login($data['FromUserName'], $EventKey['domain_id'], $data['Ticket']);
break;
default:
// 其他业务代码
break;
}
}
}
//已关注用户
protected function scan($data)
{
// 合并配置数据和订单数据
$EventKey = json_decode($data['EventKey'], true);
switch ($EventKey['type']) {
case 1:
// 其他业务代码
break;
case 2:
Admin::bind_wechat_start($EventKey['admin_id'], $data['FromUserName'], $EventKey['domain_id'], $data['Ticket']);
break;
case 3:
Admin::wechat_login($data['FromUserName'], $EventKey['domain_id'], $data['Ticket']);
break;
default:
# code...
break;
}
}
}
3.开始绑定
绑定、解绑、检测绑定 。 把业务代码替换成自己的或者删除。 绑定是在登录状态下才可以绑定的。 我们把scene_id 弄成json的格式 更方便我们弄多个自定义数据
public function bind_wechat()
{
$url = '/';
$admin_id = $this->auth->id;
$domain = $this->request->get('domain');
if (empty($admin_id) || empty($domain)) {
$this->error("参数错误", $url);
}
$open_id = Db::name('admin')
->where('id', $admin_id)
->where('status', 'normal')
->value('open_id');
if (!empty($open_id)) {
$this->error("已绑定过微信");
}
$domain_id = \app\admin\model\Domain::where('domain',$domain)
->where('status',1)
->value('id');
if(!$domain_id) {
$this->error("场所不存在");
}
$param = ['type' => 2, 'admin_id' => $admin_id, 'domain_id' => $domain_id];
$scene_id = json_encode($param);
$wx = new WxFunction();
$qrcodeRes = json_decode($wx->getQrcodeUrl($scene_id, 'QR_STR_SCENE'), true);
if (empty($qrcodeRes['errcode'])) {
$ticket = $qrcodeRes['ticket'];
// 通过ticket换取二维码
$qrcode = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" . $ticket;
$data['qrcode'] = $qrcode;
$data['scene_id'] = $scene_id;
Db::table('tp_admin_wechat_login_log')
->insert([
'type' => 2,
'ticket' => $ticket,
'admin_id' => $admin_id,
'domain_id' => $domain_id,
'is_login' => 0,
'create_time' => time()
]);
} else {
Log::error($qrcodeRes['errcode'] . "错误信息" . $qrcodeRes['errmsg']);
$this->error("获取二维码失败", $url);
}
$background = Config::get('fastadmin.login_background');
$background = stripos($background, 'http') === 0 ? $background : config('site.cdnurl') . $background;
$this->view->assign('background', $background);
$this->view->assign('title', "绑定微信");
$this->view->assign('data', $data);
return $this->view->fetch();
}
public function checkBind()
{
$url = '/';
$scene_id = $this->request->post('scene_id');
if (empty($scene_id)) {
$this->error("参数错误", $url);
}
$scene_id = json_decode($scene_id, true);
$admin_id = $scene_id['admin_id'];
$domain_id = $scene_id['domain_id'];
if (empty($admin_id) || empty($domain_id)) {
$this->error("参数错误", $url);
}
$open_id = Db::name('admin')
->where('id', $admin_id)
->where('domain_id', $domain_id)
->where('status', 'normal')
->value('open_id');
if (empty($open_id)) {
$this->error("暂未绑定");
}
$this->success("绑定成功");
}
public function unbind_wechat()
{
$url = '/';
$admin_id = $this->auth->id;
if (empty($admin_id)) {
$this->error("参数错误", $url);
}
Db::name('admin')
->where('id', $admin_id)
->where('status', 'normal')
->update(['open_id' => '']);
$this->success("解绑成功");
}
绑定微信页面模板 重点是 js轮询
<!DOCTYPE html>
<html lang="en">
<head>
{include file="common/meta" /}
<style type="text/css">
body {
color: #999;
background: url('{$background}');
background-size: cover;
}
a {
color: #fff;
}
.login-panel {
margin-top: 150px;
}
.login-screen {
max-width: 400px;
padding: 0;
margin: 100px auto 0 auto;
}
.login-screen .well {
border-radius: 3px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background: rgba(255, 255, 255, 0.2);
}
.login-screen .copyright {
text-align: center;
}
@media (max-width: 767px) {
.login-screen {
padding: 0 20px;
}
}
.profile-img-card {
width: 100px;
height: 100px;
margin: 10px auto;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
.profile-name-card {
text-align: center;
}
#login-form {
margin-top: 20px;
}
#login-form .input-group {
margin: auto auto 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="login-wrapper">
<div class="login-screen">
<div class="well">
<div class="login-form">
<img id="profile-img" class="profile-img-card" src="__CDN__/assets/img/avatar.png"/>
<p id="profile-name" class="profile-name-card"></p>
<form action="" method="post" id="login-form">
<div id="errtips" class="hide"></div>
<div class="input-group">
<img src="{$data.qrcode}" style="width: 250px;box-shadow:0 0 10px #F1F3F4">
<input type="hidden" name="scene_id" id="scene_id" value='{$data.scene_id}'/>
</div>
</form>
</div>
</div>
<p class="copyright"><a href="http://www.beian.miit.gov.cn">赣ICP备17005368号-1</a></p>
</div>
</div>
</div>
{include file="common/script" /}
<script src="/assets/js/jquery.min.js"></script>
<script>
let c;
// 轮询用户是否已经扫码
$(document).ready(function () {
c = setInterval(check_login, 2000); //每2秒执行一次
});
//检测用户是否已扫码
function check_login() {
const scene_id = $("input[name='scene_id']").val();
$.ajax({
url: '/index/checkBind',
data: {scene_id: scene_id},
type: 'POST',
dataType: 'JSON',
success: function (res) {
if (res.code === 1) {
// 扫码成功
alert(res.msg);
window.clearInterval(c); //终止轮询
window.location.href = '/index/index';
}
}
})
}
</script>
</body>
</html>
回调走的方法
<?php
namespace app\admin\model;
use think\Db;
use think\Exception;
use think\Model;
class Admin extends Model
{
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
public static function bind_wechat_start($admin_id, $open_id, $domain_id, $ticket)
{
if (!isset($admin_id) || !isset($open_id) || !isset($domain_id) || !isset($ticket)) {
throw new Exception("参数错误");
}
$self = new self();
$self->where('id', $admin_id)
->where('domain_id', $domain_id)
->where('status', 'normal')
->update(['open_id' => $open_id]);
Db::table('tp_admin_wechat_login_log')
->where('ticket', $ticket)
->where('admin_id', $admin_id)
->where('domain_id', $domain_id)
->where('is_login', 0)
->update(['open_id' => $open_id, 'is_login' => 1, 'update_time' => time()]);
return true;
}
public static function wechat_login($open_id, $domain_id, $ticket)
{
if (empty($open_id) || empty($domain_id) || empty($ticket)) {
throw new Exception("参数错误");
}
$admin = Admin::get(['open_id' => $open_id, 'domain_id' => $domain_id, 'status' => 'normal']);
if (!$admin) {
throw new Exception("用户不存在");
}
Db::table('tp_admin_wechat_login_log')
->where('ticket', $ticket)
->where('domain_id', $domain_id)
->where('is_login', 0)
->update(['admin_id' => $admin->id,'open_id' => $open_id, 'is_login' => 1, 'update_time' => time()]);
return true;
}
}
二、登录
登录的逻辑和绑定就差不多了 扫码后把自己的登录的逻辑就可以了
1、开始登录
public function wechat_login()
{
$domain = $this->request->get('domain');
$url = '/';
if ($this->auth->isLogin()) {
$this->success(__("You've logged in, do not login again"), 'index/index');
}
if (empty($domain)) {
$this->error("参数错误", $url);
}
$domain_id = \app\admin\model\Domain::where('domain',$domain)
->where('status',1)
->value('id');
if(!$domain_id) {
$this->error("场所不存在");
}
$param = ['type' => 3, 'domain_id' => $domain_id];
$scene_id = json_encode($param);
$wx = new WxFunction();
$qrcodeRes = json_decode($wx->getQrcodeUrl($scene_id, 'QR_STR_SCENE'), true);
if (empty($qrcodeRes['errcode'])) {
$ticket = $qrcodeRes['ticket'];
// 通过ticket换取二维码
$qrcode = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" . $ticket;
$data['qrcode'] = $qrcode;
$data['scene_id'] = $scene_id;
$data['ticket'] = $ticket;
Db::table('tp_admin_wechat_login_log')
->insert([
'type' => 2,
'ticket' => $ticket,
'admin_id' => 0,
'domain_id' => $domain_id,
'is_login' => 0,
'create_time' => time()
]);
} else {
Log::error($qrcodeRes['errcode'] . "错误信息" . $qrcodeRes['errmsg']);
$this->error("获取二维码失败", $url);
}
$background = Config::get('fastadmin.login_background');
$background = stripos($background, 'http') === 0 ? $background : config('site.cdnurl') . $background;
$this->view->assign('background', $background);
$this->view->assign('title', "微信登录");
$this->view->assign('data', $data);
return $this->view->fetch();
}
public function checkLogin()
{
$url = '/';
$scene_id = $this->request->post('scene_id');
$ticket = $this->request->post('ticket');
if (empty($scene_id) || empty($ticket)) {
$this->error("参数错误", $url);
}
$scene_id = json_decode($scene_id, true);
$domain_id = $scene_id['domain_id'];
if (empty($domain_id)) {
$this->error("参数错误", $url);
}
$is_login = Db::table('tp_admin_wechat_login_log')
->where('ticket', $ticket)
->where('domain_id', $domain_id)
->where('is_login', 1)
->find();
if (empty($is_login)) {
$this->error("暂未扫码登录");
}
$domain_field = [
'md.id' => 'id',
'md.name' => 'name',
'md.domain' => 'domain',
'md.MCHID' => 'MCHID',
'da.proportion' => 'proportion',
];
$domain = Db::table('main_domain')
->alias('md')
->field($domain_field)
->join(['dls_admin' => 'da'], 'md.pid=da.id', 'left')
->where('md.id', $domain_id)
->where('md.status', '1')
->find();
if (!$domain) {
$this->error("场所不存在");
}
if (is_null($domain['proportion']) or !isset($domain['proportion'])) {
$domain['proportion'] = 0;
}
if (is_null($domain)) {
return false;
}
$admin_id = $is_login['admin_id'];
$admin = Admin::get(['id' => $admin_id, 'domain_id' => $domain['id'], 'open_id' => $is_login['open_id']]);
if(!$admin) {
$this->error("用户不存在");
}
$admin->loginfailure = 0;
$admin->logintime = time();
$admin->token = Random::uuid();
$admin->save();
$admin->domain = [
'domain_id' => $domain['id'],
'domain_name' => $domain['name'],
'proportion' => $domain['proportion'],
];
$admin->wechat = [
'MCHID' => $domain['MCHID'],
];
Session::set("admin", $admin->toArray());
$keeptime = 86400;
$expiretime = time() + $keeptime;
$key = md5(md5($admin_id) . md5($keeptime) . md5($expiretime) . $admin->token);
$data = [$admin_id, $keeptime, $expiretime, $key];
Cookie::set('keeplogin', implode('|', $data), 86400 * 30);
$this->success("登录成功");
}
<!DOCTYPE html>
<html lang="en">
<head>
{include file="common/meta" /}
<style type="text/css">
body {
color: #999;
background: url('{$background}');
background-size: cover;
}
a {
color: #fff;
}
.login-panel {
margin-top: 150px;
}
.login-screen {
max-width: 400px;
padding: 0;
margin: 100px auto 0 auto;
}
.login-screen .well {
border-radius: 3px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background: rgba(255, 255, 255, 0.2);
}
.login-screen .copyright {
text-align: center;
}
@media (max-width: 767px) {
.login-screen {
padding: 0 20px;
}
}
.profile-img-card {
width: 100px;
height: 100px;
margin: 10px auto;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
.profile-name-card {
text-align: center;
}
#login-form {
margin-top: 20px;
}
#login-form .input-group {
margin: auto auto 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="login-wrapper">
<div class="login-screen">
<div class="well">
<div class="login-form">
<img id="profile-img" class="profile-img-card" src="__CDN__/assets/img/avatar.png"/>
<p id="profile-name" class="profile-name-card"></p>
<form action="" method="post" id="login-form">
<div id="errtips" class="hide"></div>
<div class="input-group">
<img src="{$data.qrcode}" style="width: 250px;box-shadow:0 0 10px #F1F3F4">
<input type="hidden" name="scene_id" id="scene_id" value='{$data.scene_id}'/>
<input type="hidden" name="ticket" id="ticket" value='{$data.ticket}'/>
</div>
</form>
</div>
</div>
<p class="copyright"><a href="http://www.beian.miit.gov.cn">赣ICP备17005368号-1</a></p>
</div>
</div>
</div>
{include file="common/script" /}
<script src="/assets/js/jquery.min.js"></script>
<script>
let c;
// 轮询用户是否已经扫码
$(document).ready(function () {
c = setInterval(check_login, 2000); //每2秒执行一次
});
//检测用户是否已扫码
function check_login() {
const scene_id = $("input[name='scene_id']").val();
const ticket = $("input[name='ticket']").val();
$.ajax({
url: '/index/checkLogin',
data: {scene_id: scene_id, ticket: ticket},
type: 'POST',
dataType: 'JSON',
success: function (res) {
if (res.code === 1) {
// 扫码成功
// alert(res.msg);
window.clearInterval(c); //终止轮询
window.location.href = '/index/index';
}
}
})
}
</script>
</body>
</html>
2、加按钮绑定、解绑
<div class="form-group">
<a onclick="unbind_wechat()" class="btn btn-danger btn-sm">解除绑定微信</a>
<a href="/index/bind_wechat" target="_blank" class="btn btn-info btn-sm">绑定微信</a>
</div>
<script src="/assets/js/jquery.min.js"></script>
<script>
function unbind_wechat() {
$.ajax({
url: '/index/unbind_wechat',
data: {},
type: 'POST',
dataType: 'JSON',
success: function (res) {
if (res.code === 1) {
alert(res.msg);
}
}
})
}
</script>
参考文章: