一、引言

在当今Web应用中,快捷登录已成为提升用户体验的关键功能。微信作为国内用户量最大的社交平台之一,其扫码登录功能被广泛应用于各类网站和应用中。本文将详细解析一个基于WebSocket实时通信的微信扫码登录系统,该系统通过PHP+JavaScript实现,结合微信OAuth2.0授权机制与WebSocket实时消息传递,实现了"PC端展示二维码-手机微信扫码-实时同步登录状态"的完整流程。

二、功能概述

本系统核心目标是实现"微信扫码快速登录",主要包含以下功能:

  1. 登录页功能:生成唯一客户端ID,建立WebSocket连接,展示包含客户端ID的登录二维码
  2. 扫码页功能:用户扫码后获取微信OpenID,通过WebSocket将OpenID发送至登录页
  3. 实时通信:基于WebSocket实现登录页与扫码页的双向消息传递,实时同步扫码状态、验证结果
  4. OpenID验证:检查扫码用户的OpenID是否已注册,完成登录状态确认
  5. 状态同步:登录成功/失败状态实时反馈到登录页,完成登录流程闭环

三、核心技术栈

本系统融合了多种前后端技术,核心技术栈如下:

  • 后端语言:PHP(处理参数验证、微信OpenID获取、会话管理)
  • 前端技术:HTML5 + JavaScript(页面渲染、WebSocket客户端、二维码生成)
  • 实时通信:WebSocket(实现登录页与扫码页的实时消息交互)
  • 微信授权:微信OAuth2.0(通过授权流程获取用户OpenID)
  • 二维码生成:QR_1.js(前端生成包含登录信息的二维码)
  • 异步请求:Ajax(检查OpenID是否已注册)

四、程序结构解析

4.1 PHP核心处理部分(后端逻辑)

PHP部分主要负责参数校验、状态区分及微信OpenID获取,代码位于文件开头和结尾:

// 参数校验与状态定义
(!isset($_GET['Action'])) && exit('缺少必要参数 Action');
$Action = @$_GET['Action'];
$IsLogin = ($Action == 'Login'); // 登录页标识
$IsScan = ($Action == 'Scan');  // 扫码页标识
(!$IsLogin && !$IsScan) && exit("无效的 Action 参数 $Action");

// 扫码时获取微信OpenID和客户端ID
$OpenID = $IsScan? GetOpenID()['openid'] : '';
$Client_ID = $IsScan? $_GET['client_id'] : '';

核心函数GetOpenID()实现了微信OAuth2.0授权流程,主要步骤包括:

  1. 配置微信参数:加载app_id、app_secret等微信开放平台配置
  2. 会话管理:使用PHP Session保存授权状态(state)与OpenID结果,避免重复授权
  3. 授权流程
    • 若未获取code,生成随机state并跳转至微信授权页
    • 授权回调后验证state合法性,防止CSRF攻击
    • 通过code调用微信接口获取access_token与OpenID
  4. 结果缓存:将获取到的OpenID及相关信息存入Session,避免重复请求微信接口

4.2 HTML页面结构(前端展示)

HTML部分根据Action参数动态生成页面内容,区分登录页与扫码页:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo $Action; ?></title>
    <?php if ($IsLogin): ?>
    <script src="js/QR_1.js"></script> <!-- 登录页加载二维码生成库 -->
    <style>/* 二维码样式 */</style>
<?php endif; ?>
</head>
<body>
    <h1><?php echo $Action; ?></h1>
    <div id="QR_Code" style="width: 200px; height: 200px;"></div> <!-- 二维码容器 -->
    <p><?php echo $IsLogin? '请使用您的手机扫描二维码以授权登录。' : ''; ?></p>
    <div id="ZT_Text"></div> <!-- 状态提示容器 -->
    <p id="OpenID"><?=$OpenID?></p> <!-- OpenID展示容器 -->
</body>
</html>
  • 登录页(Action=Login):加载二维码生成库,显示二维码容器和登录提示
  • 扫码页(Action=Scan):隐藏二维码相关元素,展示获取到的OpenID

4.3 JavaScript逻辑(核心交互)

JavaScript部分是系统的"神经中枢",负责WebSocket通信、二维码生成、消息处理等核心逻辑,主要包含以下模块:

4.3.1 基础变量定义

// 状态标识(从PHP获取)
let IsLogin = <?php echo $IsLogin? 'true' : 'false'; ?>;
let IsScan = <?php echo $IsScan? 'true' : 'false'; ?>;

// 生成随机字符串(用于客户端ID)
function XL_RandomStr(Len=32) {
    const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    return Array.from({ length: Len }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

// 客户端ID(唯一标识当前页面实例)
let Client_ID = 'Login_' + (IsScan? 'Scan_' : '')  + Date.now() + XL_RandomStr(16);
let Socket = null; // WebSocket实例
let ServerUrl = `wss://www.0554h.com:9090?client_id=${Client_ID}&auth=1232131321`; // WebSocket服务地址
let To_Client_ID = IsScan? '<?php echo $Client_ID; ?>' : ''; // 目标客户端ID(扫码页指向登录页)

4.3.2 WebSocket通信模块

WebSocket是实现实时通信的核心,包含连接建立、消息发送、消息接收、连接关闭等功能:

// 建立WebSocket连接
function WebSocket_Open() {
    if (Socket && Socket.readyState === WebSocket.OPEN) return;
    Socket = new WebSocket(ServerUrl);

    // 连接成功回调
    Socket.onopen = () => { 
        if (IsLogin) {
            ZTElement.innerHTML = '连接成功 客户端ID: ' + Client_ID + ' 等待微信扫码登录~!';
            ShowQRcode(); // 登录页生成二维码
        }else if (IsScan) {
            // 扫码页发送"正在扫码"消息
            const Send_OpenID = {
                Type: 'Login',
                Action: 'Scanning',
                From: Client_ID,
                To: To_Client_ID
            };
            sendMessage(Send_OpenID);
        }
    };

    // 错误处理
    Socket.onerror = (error) => { ZTElement.innerHTML = '<span style="color: red;">连接错误: ' + error.message + '</span>'; };

    // 关闭处理
    Socket.onclose = (event) => { ZTElement.innerHTML = '<span style="color: red;">连接关闭</span>'; };

    // 消息接收处理
    Socket.onmessage = (event) => { 
        if (!/^\{.*\}$/.test(event.data)) return; // 验证JSON格式
        const Msg = JSON.parse(event.data);
        if (Msg.Type === 'Login') {
            // 根据消息Action处理不同逻辑(扫码中、OpenID发送、注册状态等)
            switch (Msg.Action) {
                case 'Send_OpenID': /* 处理OpenID接收与验证 */ break;
                case 'Scanning': /* 显示扫码中状态 */ break;
                case 'UnRegister': /* 显示未注册状态 */ break;
                case 'Register': /* 显示已注册状态 */ break;
            }
        }
    };
}

// 发送消息
function sendMessage(SendContent) {
    if (!Socket || Socket.readyState !== WebSocket.OPEN) return;
    const messageStr = JSON.stringify(SendContent);
    try {
        Socket.send(messageStr);
    } catch (error) {
        ZTElement.innerHTML = '<span style="color: red;">发送失败: ' + error.message + '</span>';
    }
}

// 关闭连接
function WebSocket_Close() {
    if (!Socket || Socket.readyState !== WebSocket.OPEN) return;
    Socket.close(1000, '客户端主动断开连接');
}

4.3.3 二维码生成模块

登录页通过ShowQRcode()函数生成包含客户端ID的二维码,供微信扫码:

function ShowQRcode() {
    QR_Code.style.display = 'block';
    try {
        if (QRCodeElement && typeof qrcode !== 'undefined') {
            var QR = qrcode(0, 'H'); // 高纠错级别
            // 二维码内容:扫码页地址+当前客户端ID(确保扫码后能定位到当前登录页)
            QR.addData(`http://0554h.com/API/Socket/Socket_Login.php?Action=Scan&client_id=${Client_ID}`);
            QR.make();
            var moduleCount = QR.getModuleCount();
            var margin = 0;
            var cellSize = (200 - 2 * margin) / moduleCount; // 自适应200px容器
            var imgTag = QR.createImgTag(cellSize, margin);
            QRCodeElement.innerHTML = imgTag;
        } else {
            console.error('二维码功能不可用 - 请检查js/QR_1.js是否正确加载');
        }
    } catch (error) {
        console.error('生成二维码失败:', error);
    }
}

4.3.4 OpenID验证与登录模块

通过Ajax检查OpenID是否已注册,并完成登录流程:

// 检查OpenID是否注册
function IsRegister($OpenID) {
    if (empty($OpenID)) return false;
    // 同步Ajax请求(确保验证完成后再继续)
    $.ajax({
        url: 'http://0554h.com/API/DB/DB_Login.php',
        type: 'POST',
        data: { Action: 'Check_Register', OpenID: $OpenID },
        async: false, // 同步请求(注意:同步请求可能阻塞UI,实际应用可优化为异步+回调)
        success: function(response) {
            return response.success;
        }
    });
}

// 登录逻辑(简化版,实际应用需添加Session/Cookie设置等)
function Login(OpenID) {
    // 此处可添加登录状态保存逻辑(如设置Session、生成Token等)
}

4.3.5 页面初始化

页面加载完成后初始化DOM元素,建立WebSocket连接,并启动扫码页的OpenID监测:

document.addEventListener('DOMContentLoaded', function() {
    // 获取DOM元素
    QRCodeElement = document.getElementById('QR_Code');
    ZTElement = document.getElementById('ZT_Text');
    OpenID = document.getElementById('OpenID').innerHTML;

    // 建立WebSocket连接
    WebSocket_Open();

    // 扫码页:实时监测OpenID并发送给登录页
    if(IsScan) {
        setInterval(() => {
            if(OpenID != '' && OpenID2 != OpenID && Socket.readyState === WebSocket.OPEN) {
                const Send_OpenID = { 
                    Type: 'Login', 
                    Action: 'Send_OpenID', 
                    From: Client_ID, 
                    To: To_Client_ID, 
                    OpenID: OpenID 
                };
                sendMessage(Send_OpenID);
                OpenID2 = OpenID;
            }
        }, 500);
    }
});

五、完整工作流程

  1. 用户访问登录页(Action=Login)

    • PHP验证Action参数,标记IsLogin=true
    • 前端生成唯一Client_ID,建立WebSocket连接
    • 连接成功后生成二维码(内容为扫码页地址+当前Client_ID
    • 页面显示"等待微信扫码登录"状态
  2. 用户使用微信扫码

    • 微信扫码后跳转到二维码中的扫码页(Action=Scan&client_id=xxx)
    • 扫码页PHP调用GetOpenID()函数,通过微信OAuth2.0流程获取用户OpenID
    • 扫码页生成自己的Client_ID,并通过To_Client_ID记录登录页的Client_ID
    • 扫码页建立WebSocket连接,发送"Scanning"消息到登录页,登录页显示"xxx正在扫码中"
  3. OpenID验证与登录

    • 扫码页通过定时器监测到OpenID已获取,发送Send_OpenID消息(包含OpenID)到登录页
    • 登录页接收OpenID,调用IsRegister()通过Ajax检查是否注册
    • 若未注册:发送UnRegister消息,登录页显示"OpenID未注册"
    • 若已注册:发送Register消息,调用Login()完成登录,发送LoginSuccess消息,隐藏二维码并显示"登录成功",最后关闭WebSocket连接

六、关键技术点解析

  1. 客户端身份标识(Client_ID)

    • 由时间戳+随机字符串组成,确保唯一性
    • 登录页与扫码页通过Client_IDTo_Client_ID建立一对一通信关系
    • 避免多用户同时登录时的消息混淆
  2. 微信OpenID获取流程

    • 基于微信OAuth2.0的授权码模式(code模式)
    • 通过state参数防止CSRF攻击(服务器端验证state合法性)
    • 支持结果缓存(Session存储),减少重复授权请求
  3. WebSocket消息协议

    • 消息格式为JSON,包含Type(类型)、Action(动作)、From(发送方)、To(接收方)等字段
    • 基于Type=Login的消息类型,通过Action区分不同操作(扫码中、发送OpenID、验证结果等)
    • 确保消息仅在指定的客户端间传递(通过To字段校验)

七、注意事项与优化建议

  1. 配置安全

    • 微信app_idapp_secret需替换为实际值,建议通过环境变量(getenv)加载,避免硬编码
    • WebSocket连接的auth参数(示例中为1232131321)需替换为实际的身份验证逻辑,防止未授权连接
  2. WebSocket服务器

    • 需确保WebSocket服务器(wss://www.0554h.com:9090)正常运行并支持跨域请求
    • 生产环境需配置SSL证书(wss协议),确保通信安全
  3. 异步处理优化

    • IsRegister()函数使用了同步Ajax(async: false),可能导致UI阻塞,建议改为异步请求+回调函数
    • 可添加请求超时处理,避免因后端响应缓慢导致的页面卡死
  4. 错误处理增强

    • 增加WebSocket重连机制(如连接断开后自动重试)
    • 对微信授权失败、二维码生成失败等场景添加更友好的提示
  5. 安全性提升

    • 客户端ID可添加签名机制,防止伪造
    • OpenID等敏感信息在传输和存储时建议加密
    • 增加二维码有效期限制,超时后自动刷新

八、总结

本系统通过PHP+JavaScript+WebSocket技术栈,结合微信OAuth2.0授权机制,实现了一套完整的微信扫码登录方案。其核心优势在于通过WebSocket实现了登录状态的实时同步,避免了传统轮询方式的性能损耗,同时借助微信生态实现了"免输入密码"的快捷登录体验。

该系统可应用于各类需要微信快捷登录的Web应用,如管理后台、社区论坛、电商平台等。通过本文的解析,开发者可快速理解扫码登录的实现原理,并根据实际需求进行扩展和优化。

源代码

(!isset($_GET['Action'])) && exit('缺少必要参数 Action'); // 缺少必要参数 Action
$Action = @$_GET['Action']; // 获取 Action 参数
$IsLogin = ($Action == 'Login'); // 是否登录
$IsScan = ($Action == 'Scan'); // 是否扫描
(!$IsLogin && !$IsScan) && exit("无效的 Action 参数 $Action"); // 无效的 Action 参数
// 扫描时获取微信OpenID
$OpenID = $IsScan? GetOpenID()['openid'] : '';
// 扫描时获取登录页面客户端ID
$Client_ID = $IsScan? $_GET['client_id'] : '';
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo $Action; ?></title>
    <?php if ($IsLogin): ?>
    <script src="js/QR_1.js"></script>
    <style>
        #QR_Code {
            margin: 20px auto;
            /* 默认隐藏 */
            display: none;
        }
    </style>
<?php endif; ?>
</head>
<body>
    <h1><?php echo $Action; ?></h1>
    <div id="QR_Code" style="width: 200px; height: 200px;"></div>
    <p><?php echo $IsLogin? '请使用您的手机扫描二维码以授权登录。' : ''; ?></p>
    <div id="ZT_Text"></div>
    <p id="OpenID"><?=$OpenID?></p>
<script>
    // 登录状态
    let IsLogin = <?php echo $IsLogin? 'true' : 'false'; ?>;
    // 扫描状态
    let IsScan = <?php echo $IsScan? 'true' : 'false'; ?>;
    // 生成随机字符串 RandomStr 长度为 Len
    function XL_RandomStr(Len=32) {
        const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        return Array.from({ length: Len }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
    }
    let OpenID2 = XL_RandomStr();
    // 客户端ID
    let Client_ID = 'Login_' + (IsScan? 'Scan_' : '')  + Date.now() + XL_RandomStr(16);
    // 定义DOM元素变量
    let QRCodeElement, ZTElement, OpenID;
    // 其他全局变量
    let Socket = null;
    let ServerUrl = `wss://www.0554h.com:9090?client_id=${Client_ID}&auth=1232131321`;
    // 登录页面客户端ID
    let To_Client_ID = IsScan? '<?php echo $Client_ID; ?>' : '';

    function WebSocket_Open() {
        if (Socket && Socket.readyState === WebSocket.OPEN) return;
        Socket = new WebSocket(ServerUrl);
        // ------------------------------------------------------------------------------------
        // 连接成功
        Socket.onopen = () => { 
            if (IsLogin) {
                ZTElement.innerHTML = '连接成功 客户端ID: ' + Client_ID + ' 等待微信扫码登录~!';
                // 生成二维码
                ShowQRcode();
            }else if (IsScan) {
                // 发送OpenID消息内容
                const Send_OpenID = {
                    Type: 'Login',
                    Action: 'Scanning',
                    From: Client_ID,
                    To: To_Client_ID
                };
                // 发送OpenID
                sendMessage(Send_OpenID);
            };
        };
        // ------------------------------------------------------------------------------------
        // 连接错误
        Socket.onerror = (error) => { ZTElement.innerHTML = '<span style="color: red;">连接错误: ' + error.message + '</span>'; };
        // ------------------------------------------------------------------------------------
        // 连接关闭
        Socket.onclose = (event) => { ZTElement.innerHTML = '<span style="color: red;">连接关闭</span>'; };
        // ------------------------------------------------------------------------------------
        // 接收消息
        Socket.onmessage = (event) => { 
            //检查消息格式是否为JSON
            if (!/^\{.*\}$/.test(event.data)) return;
            //解析消息
            const Msg = JSON.parse(event.data);
            // 处理不同类型的消息
            if (Msg.Type === 'Login') {
                switch (Msg.Action) {
                    case 'Send_OpenID':
                        // 检查是否是目标客户端
                        if (Msg.To !== Client_ID) return;
                        // 状态提示
                        ZTElement.innerHTML = '接收到OpenID: ' + Msg.OpenID;
                        // 检查OpenID是否注册
                        if (IsRegister(Msg.OpenID)) {
                            ZTElement.innerHTML = '<span style="color: red;">OpenID未注册</span>';
                            // 发送未注册消息内容
                            const Send_UnRegister = {
                                Type: 'Login',
                                Action: 'UnRegister',
                                From: Client_ID,
                                To: To_Client_ID,
                                OpenID: Msg.OpenID
                            };
                            // 发送未注册消息
                            sendMessage(Send_UnRegister);
                            break;
                        }else{
                            ZTElement.innerHTML = '<span style="color: green;">OpenID已注册</span>';
                            // 发送已注册消息内容
                            const Send_Register = {
                                Type: 'Login',
                                Action: 'Register',
                                From: Client_ID,
                                To: To_Client_ID,
                                OpenID: Msg.OpenID
                            };
                            // 发送已注册消息
                            sendMessage(Send_Register);
                            // 登录
                            Login(Msg.OpenID);
                            // 发送登录成功消息
                            const Send_LoginSuccess = {
                                Type: 'Login',
                                Action: 'LoginSuccess',
                                From: Client_ID,
                                To: To_Client_ID,
                                OpenID: Msg.OpenID
                            };
                            // 发送登录成功消息
                            sendMessage(Send_LoginSuccess);
                            // 登录成功
                            IsLogin = true;
                            // 隐藏二维码
                            QR_Code.style.display = 'none';
                            // 状态提示
                            ZTElement.innerHTML = '登录成功 客户端ID: ' + Client_ID ;
                            // 关闭连接
                            WebSocket_Close();
                            break;
                        }
                        break;
                    case 'Scanning':
                        ZTElement.innerHTML = Msg.From + ' 正在扫码中...'; 
                        break;
                    case 'UnRegister':
                        ZTElement.innerHTML = '<span style="color: red;">OpenID未注册</span>';
                        break;
                    case 'Register':
                        ZTElement.innerHTML = '<span style="color: green;">OpenID已注册</span>';
                        break;
                }
            }
        };
    }
    // ------------------------------------------------------------------------------------
    // 发送消息
    function sendMessage(SendContent) {
        if (!Socket || Socket.readyState !== WebSocket.OPEN) return;
        const messageStr = JSON.stringify(SendContent);
        try {
            Socket.send(messageStr);
        } catch (error) {
            ZTElement.innerHTML = '<span style="color: red;">发送失败: ' + error.message + '</span>';
        }
    }
    // ------------------------------------------------------------------------------------
    // 断开WebSocket连接
    function WebSocket_Close() {
        if (!Socket || Socket.readyState !== WebSocket.OPEN) return;
        Socket.close(1000, '客户端主动断开连接');
        ZTElement.innerHTML = '<span style="color: red;">断开连接成功</span>';
    }
    // 显示二维码
    function ShowQRcode() {
        QR_Code.style.display = 'block';
        try {
            if (QRCodeElement && typeof qrcode !== 'undefined') {
                var QR = qrcode(0, 'H'); // 自动检测类型,高纠错级别
                QR.addData(`http://0554h.com/API/Socket/Socket_Login.php?Action=Scan&client_id=${Client_ID}`);
                QR.make(); // 生成二维码
                var moduleCount = QR.getModuleCount(); // 获取模块数量
                var margin = 0; // 二维码外边距
                var cellSize = (200 - 2 * margin) / moduleCount; // 计算单元格大小以适应200px容器
                var imgTag = QR.createImgTag(cellSize, margin); // 创建img标签
                QRCodeElement.innerHTML = imgTag; // 插入二维码图片
            } else {
                console.error((!QRCodeElement)? '未找到二维码元素' : '二维码功能不可用 - 请检查js/QR_1.js是否正确加载');
            }
        } catch (error) {
            console.error('生成二维码失败:', error);
        }
    }

    // 检查OpenID是否注册
    function IsRegister($OpenID) {
        // 检查OpenID是否存在
        if (empty($OpenID)) {
            return false;
        }
        // 使用Ajax检查OpenID是否注册
        $.ajax({
            url: 'http://0554h.com/API/DB/DB_Login.php',
            type: 'POST',
            data: { Action: 'Check_Register', OpenID: $OpenID },
            async: false,
            success: function(response) {
                return response.success;
            }
        });

    }

    // 页面加载完成后执行
    document.addEventListener('DOMContentLoaded', function() {
        // 获取DOM元素
        QRCodeElement = document.getElementById('QR_Code');
        ZTElement = document.getElementById('ZT_Text');
        OpenID = document.getElementById('OpenID').innerHTML;
        // 打开WebSocket连接
        WebSocket_Open();
        // 实时监测OpenID已获取到数据
        if(IsScan) {
            setInterval(() => {
                if(OpenID != '' && OpenID2 != OpenID && Socket.readyState === WebSocket.OPEN) {
                    // 发送OpenID消息内容
                    const Send_OpenID = { Type: 'Login', Action: 'Send_OpenID', From: Client_ID, To: To_Client_ID, OpenID: OpenID };
                    // 发送OpenID
                    sendMessage(Send_OpenID);
                    OpenID2 = OpenID;
                }
            }, 500);
        }

    });
</script>
</body>
</html>
<?php
// 获取微信OpenID
function GetOpenID(bool $forceRefresh = false)
{
    // ===== 配置区域:在正式环境中请将以下占位值替换为真实的微信开放平台参数 =====
    $config = [
        'app_id'     => getenv('WECHAT_APP_ID') ?: 'wx194*************',
        'app_secret' => getenv('WECHAT_APP_SECRET') ?: '8c40048*************************',
        'scope'      => 'snsapi_base', // 可根据业务需要改为 snsapi_base  snsapi_userinfo
    ];
    // ======================================================================

    if (empty($config['app_id'])) {
        return [
            'success' => false,
            'message' => '微信 AppID 未配置,请在 GetOpenID 函数中提供有效的 app_id。',
        ];
    }
    if (empty($config['app_secret'])) {
        return [
            'success' => false,
            'message' => '微信 AppSecret 未配置,请在 GetOpenID 函数中提供有效的 app_secret。',
        ];
    }

    // Session 用于保存 state 与结果
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    $sessionKeyPayload = 'wechat_openid_payload';
    $sessionKeyState   = 'wechat_oauth_state';

    if (!$forceRefresh && isset($_SESSION[$sessionKeyPayload]) && is_array($_SESSION[$sessionKeyPayload])) {
        return array_merge(['success' => true, 'cached' => true], $_SESSION[$sessionKeyPayload]);
    }

    $code  = isset($_GET['code']) ? trim($_GET['code']) : '';
    $state = isset($_GET['state']) ? trim($_GET['state']) : '';

    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host   = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $uri    = $_SERVER['REQUEST_URI'] ?? '/';
    $currentUrl = $scheme . '://' . $host . $uri;

    if ($code === '') {
        if (function_exists('random_bytes')) {
            $stateValue = bin2hex(random_bytes(16));
        } elseif (function_exists('openssl_random_pseudo_bytes')) {
            $stateValue = bin2hex(openssl_random_pseudo_bytes(16));
        } else {
            $stateValue = bin2hex(str_pad((string)mt_rand(), 16, '0', STR_PAD_LEFT));
        }
        $_SESSION[$sessionKeyState] = $stateValue;

        $authParams = [
            'appid'         => $config['app_id'],
            'redirect_uri'  => $currentUrl,
            'response_type' => 'code',
            'scope'         => $config['scope'],
            'state'         => $stateValue,
        ];
        $authUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($authParams, '', '&', PHP_QUERY_RFC3986) . '#wechat_redirect';

        if (!headers_sent()) {
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
            header('Pragma: no-cache');
            header('Location: ' . $authUrl);
        } else {
            echo '<script>window.location.href=' . json_encode($authUrl, JSON_UNESCAPED_SLASHES) . ';</script>';
        }
        exit;
    }

    if (!isset($_SESSION[$sessionKeyState]) || !hash_equals($_SESSION[$sessionKeyState], $state)) {
        unset($_SESSION[$sessionKeyState]);
        return [
            'success' => false,
            'message' => 'state 校验失败,可能存在 CSRF 风险或会话已过期。',
            'code'    => $code,
        ];
    }
    unset($_SESSION[$sessionKeyState]);

    $tokenUrl = sprintf(
        'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code',
        urlencode($config['app_id']),
        urlencode($config['app_secret']),
        urlencode($code)
    );

    $httpGet = function (string $url, int $timeout = 10): array {
        if (function_exists('curl_init')) {
            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL            => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT        => $timeout,
                CURLOPT_SSL_VERIFYPEER => true,
                CURLOPT_SSL_VERIFYHOST => 2,
            ]);
            $body  = curl_exec($ch);
            $error = curl_error($ch);
            $errno = curl_errno($ch);
            curl_close($ch);

            if ($errno !== 0) {
                return ['success' => false, 'message' => $error ?: 'cURL 请求失败'];
            }
            return ['success' => true, 'body' => (string)$body];
        }

        $context = stream_context_create([
            'http' => [
                'method'  => 'GET',
                'timeout' => $timeout,
                'header'  => "User-Agent: WeChatOAuthClient/1.0\r\n",
            ],
            'ssl' => [
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ],
        ]);

        $body = @file_get_contents($url, false, $context);
        if ($body === false) {
            $error = error_get_last();
            return ['success' => false, 'message' => $error['message'] ?? 'HTTP 请求失败'];
        }

        return ['success' => true, 'body' => (string)$body];
    };

    $tokenResponse = $httpGet($tokenUrl);
    if ($tokenResponse['success'] === false) {
        return [
            'success' => false,
            'message' => '访问微信接口失败:' . $tokenResponse['message'],
        ];
    }

    $tokenData = json_decode($tokenResponse['body'], true);
    if (!is_array($tokenData)) {
        return [
            'success' => false,
            'message' => '解析微信返回数据失败。',
            'raw'     => $tokenResponse['body'],
        ];
    }
    if (isset($tokenData['errcode']) && $tokenData['errcode'] !== 0) {
        return [
            'success' => false,
            'message' => '微信接口错误:' . ($tokenData['errmsg'] ?? '未知错误'),
            'code'    => $tokenData['errcode'],
        ];
    }
    if (empty($tokenData['openid'])) {
        return [
            'success' => false,
            'message' => '微信返回数据中缺少 OpenID。',
            'data'    => $tokenData,
        ];
    }

    $result = [
        'success'       => true,
        'cached'        => false,
        'openid'        => $tokenData['openid'],
        'access_token'  => $tokenData['access_token'] ?? null,
        'refresh_token' => $tokenData['refresh_token'] ?? null,
        'expires_in'    => $tokenData['expires_in'] ?? null,
        'scope'         => $tokenData['scope'] ?? null,
        'unionid'       => $tokenData['unionid'] ?? null,
    ];

    $_SESSION[$sessionKeyPayload] = $result;
    return $result;

}