django 网站接入微信登录,在手机非微信浏览器端登录,想用微信开放平台的 h5 唤起微信客户端登录,代码如下,怎么还是总提示:请在微信客户端打开链接呢?

105 天前
 python30

django 网站接入微信登录,在手机非微信浏览器端登录,想用微信开放平台的 h5 唤起微信客户端登录,代码如下,怎么还是总提示:请在微信客户端打开链接呢?

views.py

def generate_state():
    """
    生成一个随机的 state 参数
    """
    return secrets.token_urlsafe(16)  # 生成一个 16 字节的随机字符串

def wechat_login(request):
    """
    微信登录入口,根据设备类型选择不同的授权方式
    """
    user_agent = request.META.get('HTTP_USER_AGENT', '').lower()

    # 判断是否为微信浏览器
    is_wechat_browser = 'micromessenger' in user_agent

    # 判断是否为手机端
    is_mobile = any(keyword in user_agent for keyword in ['mobile', 'android', 'iphone', 'ipod'])

    # 生成随机的 state 参数
    state = generate_state()
    request.session['wechat_state'] = state  # 将 state 存储在会话中


    if is_wechat_browser:
        # 微信浏览器内登录(使用微信公众号的 OAuth2.0 授权)
        wechat_auth_url = (
            f"https://open.weixin.qq.com/connect/oauth2/authorize"
            f"?appid={settings.WECHAT_MP_APP_ID}"  # 使用微信公众号的 AppID
            f"&redirect_uri={settings.WECHAT_MP_REDIRECT_URI}"  # 微信公众号的回调地址
            f"&response_type=code"
            f"&scope=snsapi_userinfo"  # 使用 snsapi_userinfo 或 snsapi_base
            f"&state={state}#wechat_redirect"
        )
    elif is_mobile:
        # 手机端浏览器登录(使用微信开放平台的 H5 登录)
        wechat_auth_url = (
            f"https://open.weixin.qq.com/connect/oauth2/authorize"
            f"?appid={settings.WECHAT_OPEN_APP_ID}"  # 使用微信开放平台的 AppID
            f"&redirect_uri={settings.WECHAT_OPEN_REDIRECT_URI}"  # 微信开放平台的回调地址
            f"&response_type=code"
            f"&scope=snsapi_userinfo"  # 使用 snsapi_login
            f"&state={state}#wechat_redirect"
        )
    else:
        # 电脑端浏览器登录(使用微信开放平台的扫码登录)
        wechat_auth_url = (
            f"https://open.weixin.qq.com/connect/qrconnect"
            f"?appid={settings.WECHAT_OPEN_APP_ID}"  # 使用微信开放平台的 AppID
            f"&redirect_uri={settings.WECHAT_OPEN_REDIRECT_URI}"  # 微信开放平台的回调地址
            f"&response_type=code"
            f"&scope=snsapi_login"  # 使用 snsapi_login
            f"&state={state}#wechat_redirect"
        )

    return redirect(wechat_auth_url)


def get_wechat_h5_login_url(request):
    """
    生成微信开放平台的 H5 登录 URL
    """
	# 生成随机的 state 参数
    state = generate_state()
    request.session['wechat_state'] = state
    # 微信开放平台的 H5 登录 URL
    wechat_h5_login_url = (
        f"https://open.weixin.qq.com/connect/oauth2/authorize"
        f"?appid={settings.WECHAT_OPEN_APP_ID}"  # 微信开放平台的 AppID
        f"&redirect_uri={settings.WECHAT_OPEN_REDIRECT_URI}"  # 回调地址
        f"&response_type=code"
        f"&scope=snsapi_login"  # 使用 snsapi_login
        f"&state={state}"  # 可选参数,用于防止 CSRF 攻击
        f"#wechat_redirect"
    )

    return JsonResponse({
        'success': True,
        'login_url': wechat_h5_login_url,
    })


def filter_username(nickname):
    """
    过滤掉不允许的字符,只保留字母、数字、_、-
    """
    # 只保留字母、数字、_、-
    filtered = re.sub(r'[^\w-]', '', nickname)
    return filtered

def generate_unique_username(nickname):
    """
    根据微信昵称生成唯一的用户名
    """
    # 过滤特殊字符
    base_username = filter_username(nickname)
    
    # 如果过滤后的用户名为空,使用默认用户名
    if not base_username:
        base_username = 'wechat_user'
    
    # 添加随机后缀
    random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
    username = f"{base_username}_{random_suffix}"
    
    # 检查用户名是否已存在
    while User.objects.filter(username=username).exists():
        random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
        username = f"{base_username}_{random_suffix}"
    
    return username

 
def wechat_callback(request):
    """
    微信登录回调处理
    """
    code = request.GET.get('code')
    state = request.GET.get('state')
    if not code:
        return HttpResponse('授权失败,未获取到 code')

    # 验证 state 参数
    if state != request.session.get('wechat_state'):
        return HttpResponse('非法请求,state 参数不匹配')
	
    # 清除会话中的 state
    request.session.pop('wechat_state', None)


    # 判断是否为微信浏览器
    user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
    is_wechat_browser = 'micromessenger' in user_agent

    # 根据设备类型选择 appid 和 secret
    if is_wechat_browser:
        # 微信浏览器内登录(使用微信公众号的 appid 和 secret )
        appid = settings.WECHAT_MP_APP_ID
        secret = settings.WECHAT_MP_APP_SECRET
    else:
        # 非微信浏览器登录(使用微信开放平台的 appid 和 secret )
        appid = settings.WECHAT_OPEN_APP_ID
        secret = settings.WECHAT_OPEN_APP_SECRET

    # 通过 code 获取 access_token
    token_url = (
        f"https://api.weixin.qq.com/sns/oauth2/access_token"
        f"?appid={appid}"
        f"&secret={secret}"
        f"&code={code}"
        f"&grant_type=authorization_code"
    )
    response = requests.get(token_url)
    data = response.json()

    access_token = data.get('access_token')
    openid = data.get('openid')
    unionid = data.get('unionid')  # 获取 unionid

    if not access_token or not openid:
        return HttpResponse('获取 access_token 失败')

    # 获取用户信息
    user_info_url = (
        f"https://api.weixin.qq.com/sns/userinfo"
        f"?access_token={access_token}"
        f"&openid={openid}"
    )
    response = requests.get(user_info_url)
    response.encoding = 'utf-8'
    user_info = response.json()

    # 根据 unionid 查找或创建用户
    user, created = User.objects.get_or_create(unionid=unionid)
    if created:
        # 生成唯一的用户名
        nickname = user_info.get('nickname', '微信用户')
        user.username = generate_unique_username(nickname)
        user.set_unusable_password()  # 微信登录用户不需要密码
    user.nickname = user_info.get('nickname', '')  # 更新昵称
    user.avatar = user_info.get('headimgurl', '')  # 更新头像
    user.wechat_nickname = user_info.get('nickname', '')  # 更新微信昵称
    user.save()

    # 登录用户
    login(request, user)

    return redirect('/')  # 登录成功后跳转到首页


def get_js_sdk_config(request):
    """
    获取 JS-SDK 配置
    """
    try:
        access_token = get_access_token(settings.WECHAT_MP_APP_ID, settings.WECHAT_MP_APP_SECRET)
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

    # 获取 JSAPI Ticket
    ticket_url = f"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={access_token}&type=jsapi"
    response = requests.get(ticket_url)
    jsapi_ticket = response.json().get('ticket')

    # 生成签名参数
    nonce_str = generate_nonce_str()
    timestamp = int(time.time())
    url = request.build_absolute_uri()

    # 生成签名
    signature = generate_signature(jsapi_ticket, nonce_str, timestamp, url)

    # 返回 JS-SDK 配置
    return JsonResponse({
        'appId': settings.WECHAT_MP_APP_ID,  # 公众号的 AppID
        'timestamp': timestamp,
        'nonceStr': nonce_str,
        'signature': signature,
        'wechatOpenAppId': settings.WECHAT_OPEN_APP_ID,  # 微信开放平台的 AppID
        'wechatOpenRedirectUri': settings.WECHAT_OPEN_REDIRECT_URI,  # 微信开放平台回调地址
		'wechatMpRedirectUri': settings.WECHAT_MP_REDIRECT_URI  # 微信开放平台回调地址
    })

def get_access_token(appid, appsecret):
    """
    获取微信 access_token ,并缓存
    """
    # 先从缓存中获取 access_token
    access_token = cache.get('wechat_access_token')
    if access_token:
        return access_token

    # 缓存中没有,则从微信接口获取
    url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={appsecret}"
    response = requests.get(url)
    data = response.json()
    
    if 'access_token' in data:
        access_token = data['access_token']
        # 将 access_token 缓存 7000 秒(微信的有效期是 7200 秒)
        cache.set('wechat_access_token', access_token, 7000)
        return access_token
    else:
        raise Exception(f"获取 access_token 失败: {data}")

urls.py

		path('wechat/login/', wechat_login, name='wechat_login'),
		path('wechat/callback/', wechat_callback, name='wechat_callback'),
		path('get_js_sdk_config/', get_js_sdk_config, name='get_js_sdk_config'),  # 获取 JS-SDK 配置
        path('wechat/get_wechat_h5_login_url/', get_wechat_h5_login_url, name='get_wechat_h5_login_url'),#手机端非微信浏览器登录

login.html 方式 1

<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<div class="container reg">
    <!-- 微信登录按钮 -->
    <button id="wechat-login">微信登录</button>
</div>
<script>
    document.getElementById('wechat-login').addEventListener('click', function () {
        // 判断是否为微信浏览器
        var userAgent = navigator.userAgent.toLowerCase();
        var isWechatBrowser = userAgent.indexOf('micromessenger') !== -1;
        var isMobile = /mobile|android|iphone/i.test(userAgent);

        if (isWechatBrowser || !isMobile) {
            // 微信浏览器内或电脑端,直接跳转到后端生成的微信登录 URL
            window.location.href = "/user/wechat/login/"; // 这里假设 Django 反向解析后的实际 URL
        } else {
            // 手机端非微信浏览器,使用微信开放平台的 H5 登录
            // 向后端请求微信开放平台的 H5 登录 URL
            var xhr = new XMLHttpRequest();
            xhr.open('GET', '/user/wechat/get_wechat_h5_login_url/', true); // 后端生成 H5 登录 URL 的接口
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    var response = JSON.parse(xhr.responseText);
                    if (response.success) {
                        // 跳转到微信开放平台的 H5 登录页面
                        window.location.href = response.login_url;
                    } else {
                        alert('获取微信登录链接失败,请稍后重试。');
                    }
                }
            };
            xhr.send();
        }
    });
</script>

login.html 方式 2

<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<div class="container reg">
    <!-- 微信登录按钮 -->
    <button id="wechat-login">微信登录</button>
</div>
<script>
        document.getElementById('wechat-login').addEventListener('click', function () {
            // 判断是否为微信浏览器
            var userAgent = navigator.userAgent.toLowerCase();
            var isWechatBrowser = userAgent.indexOf('micromessenger')!== -1;
            var isMobile = /mobile|android|iphone/i.test(userAgent);

            if (isWechatBrowser ||!isMobile) {
                // 微信浏览器内或电脑端,直接跳转到后端生成的微信登录 URL
                window.location.href = "/user/wechat/login/"; // 这里假设 Django 反向解析后的实际 URL
            } else {
                // 获取 JS-SDK 配置
                var xhr = new XMLHttpRequest();
                xhr.open('GET', '/user/get_js_sdk_config/', true);
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        var config = JSON.parse(xhr.responseText);
                        wx.config({
                            debug: true, // 调试模式
                            appId: config.appId, // 公众号的 AppID
                            timestamp: config.timestamp, // 时间戳
                            nonceStr: config.nonceStr, // 随机字符串
                            signature: config.signature, // 签名
                            jsApiList: ['launchApplication'] // 需要使用的 JS 接口
                        });

                        wx.ready(function () {
                            // 尝试唤醒微信客户端
                            wx.launchApplication({
                                appId: config.appId, // 这里需要替换为实际的微信 AppID
                                extraData: '',
                                success: function () {
                                    // 唤醒成功,跳转到微信登录 URL
                                    window.location.href = "/user/wechat_login/"; // 这里假设 Django 反向解析后的实际 URL
                                },
                                fail: function (res) {
                                    alert('唤醒微信客户端失败,请检查您的微信是否安装或尝试其他登录方式。');
                                }
                            });
                        });

                        wx.error(function (res) {
                            alert('JS-SDK 配置出错,请刷新页面重试。');
                        });
                    }
                };
                xhr.send();
            }
        });
    </script>

setting.py 里面也配置好了上面的 appid 相关参数与回调地址,并且在电脑 pc 端,微信浏览器内都可以成功登录了,就是在手机非浏览器端,不管怎么操作,都是提示:请在微信客户端内打开链接

麻烦各位大佬看看是哪里的原因? 一直无法用 h5 的 js-sdk 在手机端唤起微信客户端登录?谢谢

1576 次点击
所在节点    Python
2 条回复
xavierchow
104 天前
python 不是很熟,粗粗看了一下,如果你想要在非微信浏览器用微信登录的话,应该是快速登录功能: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

你现在判断到手机端非微信浏览器的时候,调用 get_wechat_h5_login_url 去拿重定向的地址,但是里面的 API 用错了,https://open.weixin.qq.com/connect/oauth2/authorize 这个是公众号网页授权登录(需要在微信浏览器内);你要唤起微信做快速登录是这个 https://open.weixin.qq.com/connect/qrconnect API ,具体流程你可以参考👆贴的官方快速登录功能的文档。
python30
104 天前
@xavierchow 谢谢,我换了这个接口后,跟在电脑 pc 端一样了,生成了一个二维码,让扫码登录,我看别的手机端网站,点微信登录,可以跟在微信浏览器内一样,确认一下就直接微信登录了,以前有 wx.launchApplication 唤醒微信登录,后来取消这个了,现在也不知道用啥办法了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://yangjunhui.monster/t/1113563

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX