微信公众平台扫码登录与后台处理详解

随着互联网应用的普及,扫码登录已成为非常常见的用户验证方式。尤其是微信公众平台的扫码登录,极大地简化了用户的登录流程。本文将介绍如何实现一个微信公众平台的扫码登录系统,并结合 Redis 和 MySQL 来处理登录状态与用户信息。

一、扫码登录流程简介

扫码登录的基本流程如下:

  1. 前端展示二维码:用户访问登录页面时,前端展示由服务器生成的微信二维码,二维码通常携带 Ticket 参数。
  2. 用户扫描二维码:用户使用微信扫描二维码,微信服务器会将扫描事件推送到我们的后端服务器,并携带 Ticket 和用户的 openid 等信息。
  3. 后端处理扫码事件:后端服务器接收到微信的扫码事件,验证该扫码行为并存储 Ticket 和用户信息。
  4. 客户端轮询登录状态:用户扫描二维码后,客户端定期向后端发起轮询请求,检查该 Ticket 是否有效,从而判断用户是否成功登录。
  5. 后端返回登录状态:如果扫码成功,后端服务器返回登录成功的状态,客户端结束轮询,完成登录流程。

二、核心技术栈

在实现微信扫码登录的过程中,我们使用以下几项技术:

  • Redis:用于暂时存储登录相关的 ticket_hashtoken,并设置有效期。
  • MySQL:用于存储用户的持久化信息,如 openid 和用户的相关信息。
  • RabbitMQ:作为消息队列,处理扫码事件的异步通信。
  • Flask:一个轻量级的 Web 框架,用于处理 HTTP 请求。

三、扫码登录系统的实现

接下来我们详细讨论每个模块的实现细节。

1. 用户扫描二维码并登录

当用户扫描二维码时,微信会向你的服务器发送事件通知,包含 FromUserName(即 openid)、Ticket 等重要信息。我们的任务是接收这些数据并进行处理。
 

def callback(ch, method, properties, body):
    """消息处理回调函数,打印接收到的JSON消息,并从Redis中获取token发送模板消息"""
    try:
        # 将消息体(字节流)解码为字符串
        message_str = body.decode('utf-8')

        # 将字符串反序列化为字典
        message_dict = json.loads(message_str)

        # 打印接收到的字典格式的消息
        logger.info(f"接收到来自 {RABBITMQ_QUEUE} 队列的消息: {message_dict}")

        # 从消息中提取 openid 和用户信息
        openid = message_dict.get('FromUserName')
        ticket = message_dict.get('Ticket', '')

        # 检查和存储用户信息
        if openid:
            check_and_store_user(openid)

        # 生成 ticket 的 hash 作为 Redis 的 key
        if ticket:
            ticket_hash = hashlib.sha256(ticket.encode()).hexdigest()
            logger.info(f"Generated ticket hash for {openid}: {ticket_hash}")

            # 生成安全的随机 token
            token = secrets.token_urlsafe()
            logger.info(f"Generated random token for {openid}: {token}")

            # 存储 ticket_hash 和 token 到 Redis(db1),并设置 5 分钟(300秒)的过期时间
            redis_client_db1.set(ticket_hash, token, ex=300)  # 300秒 = 5分钟
            logger.info(f"Stored ticket hash and token in Redis DB 1 with 5 min expiry: {ticket_hash} -> {token}")

        if openid:
            # 从 Redis (db0) 中获取全局的 access_token
            global_access_token = redis_client_db0.get('global_access_token')

            if not global_access_token:
                logger.error("从 Redis 获取 global_access_token 失败")
                return

            # 构造模板消息的数据,通知用户成功登录
            template_message_payload = {
                "touser": openid,
                "template_id": "C7CaKWQHaz9vc4x0fKWzU6-b62VKINsuWgAk",
                "url": "",
                "data": {
                    "time": {"value": "现在时刻", "color": "#173177"},
                    "status": {"value": "登录成功", "color": "#173177"},
                    "ip": {"value": "用户IP", "color": "#173177"}
                }
            }

            # 发送模板消息到微信服务器
            response = requests.post(f"{SEND_MESSAGE_URL}?access_token={global_access_token}",
                                     json=template_message_payload)

            # 检查响应状态
            if response.status_code == 200:
                logger.info(f"模板消息成功发送给用户 {openid}.")
            else:
                logger.error(f"模板消息发送失败: {response.text}")

        else:
            logger.error("消息中未找到 FromUserName 字段")

    except json.JSONDecodeError as e:
        # 如果消息不是合法的JSON格式,捕获异常
        logger.error(f"无法解析接收到的消息: {str(e)}")
2. Redis 存储 ticket_hashtoken

为了保证用户登录状态的有效性,我们将 ticket_hash 和随机生成的 token 存储在 Redis 中,并设置 5 分钟的过期时间。
 

# 生成 ticket 的 hash 作为 Redis 的 key
if ticket:
    ticket_hash = hashlib.sha256(ticket.encode()).hexdigest()
    logger.info(f"Generated ticket hash for {openid}: {ticket_hash}")

    # 生成安全的随机 token
    token = secrets.token_urlsafe()
    logger.info(f"Generated random token for {openid}: {token}")

    # 存储 ticket_hash 和 token 到 Redis(db1),并设置 5 分钟(300秒)的过期时间
    redis_client_db1.set(ticket_hash, token, ex=300)  # 300秒 = 5分钟
    logger.info(f"Stored ticket hash and token in Redis DB 1 with 5 min expiry: {ticket_hash} -> {token}")

通过 Redis 的过期时间控制,我们确保登录票据 ticket_hash 在规定时间内有效,超时后会自动过期删除,保证了系统的安全性。

3. 检查用户是否是新用户

每次扫码时,我们都要检查该用户是否是新用户。如果是新用户,我们需要将 openid 等用户信息写入 MySQL,以便日后管理和查询。
 

def check_and_store_user(openid):
    """检查用户是否已存在,若不存在则存储新用户信息"""
    conn = get_mysql_connection()
    cursor = conn.cursor()

    # 检查用户是否已存在
    cursor.execute("SELECT * FROM users WHERE wechat_open_id = %s", (openid,))
    user = cursor.fetchone()

    if user:
        # 用户已存在,返回相关信息
        logger.info(f"用户 {openid} 已存在.")
    else:
        # 用户不存在,存储用户信息
        cursor.execute("INSERT INTO users (wechat_open_id) VALUES (%s)", (openid,))
        conn.commit()
        logger.info(f"存储新用户 {openid}")

    cursor.close()
    conn.close()
4. 客户端轮询登录状态

当用户扫描二维码后,客户端将通过轮询请求不断检查该 ticket_hash 是否已经存在于 Redis 中。一旦找到对应的 ticket_hash,说明用户已经扫码成功,服务器可以返回登录状态给客户端。

四、总结

微信扫码登录的整个流程包含多个步骤,涉及 Redis 存储登录状态、MySQL 管理用户信息、RabbitMQ 进行异步消息处理等多个环节。通过将这些模块有机地结合在一起,能够有效地实现扫码登录、登录状态管理及用户信息的持久化。

通过这种机制,不仅简化了用户的登录流程,还大大提高了系统的安全性和扩展性。在实际项目中,可以根据不同的业务需求,进一步优化这些流程。

希望通过本文的介绍,大家能够对微信扫码登录有一个全面的理解,并能够在自己的项目中加以实现和应用。

你可能感兴趣的:(后端开发,微信公众平台)