django 微信公衆號開發 - 獲取受權 綁定用戶微信 openid

官方文檔html

流程稍微有點複雜,可是這個比較重要,經過受權得到用戶 openid 才能進行給用戶主動推送消息等功能。python

特別注意:這個必需要微信公衆號(我的的訂閱號不能用這個功能)進行了微信認證才能使用。還有回調域名須要是 https 的,如今有地方能夠申請到免費 https 證書,能夠參考個人這篇文章 百度智能雲免費 SSL 證書的申請與部署(nginx HTTPS 配置,http 請求重定向)nginx

數據庫(Model)創建保存用戶 openid 的字段

由於咱們讓用戶受權的目的就是獲得 openid,得到 openid 後要存到數據庫中,並和用戶創建對應關係。web

我用的是 django 自帶的用戶系統,官方推薦創建一個自定義的 UserProfile 之類的 Model 保存用戶相關信息。數據庫

from django.db import models
from django.contrib.auth.models import User


class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, default=None)
    openid = models.CharField("微信 openid", max_length=32)

openid 的長度貌似是 28 位,這裏定義的最大長度 32 應該夠用。django

參考json

跳轉到用戶受權頁面

這個頁面是微信的頁面,須要咱們加上參數,讓用戶跳轉過去,url 是api

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

appid 去公衆號平臺 - > 開發 -> 基本配置 裏找。服務器

redirect_uri 是成功之後的回調地址,是你本身的一個 url,要經過 urlEncode 進行編碼微信

response_type 寫 code

scope 可選填 snsapi_base 或 snsapi_userinfo

state 非必填,因此直接不加這個參數了

關於 python 的 urlEncode 可使用:

import urllib.parse
s='https://xxx.com/api/weixin_bind_callback/'
urllib.parse.quote(s)

結果是:'https%3A//xxx.com/api/weixin_bind_callback/'

它沒有處理斜槓,添加一個 safe 參數就能夠處理斜槓了

import urllib.parse
s='https://xxx.com/api/wexin_bind_callback/'
urllib.parse.quote(s, safe='')

結果是:'https%3A%2F%2Fxxx.com%2Fapi%2Fwexin_bind_callback%2F'

個人這個 url 的最終結果是:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=XXXX&redirect_uri=https%3A%2F%2FXXXX.com%2Fweixin%2Fbind%2Fcallback%2F&response_type=code&scope=snsapi_userinfo#wechat_redirect

用戶受權後經過 code 獲取 access_token

下面的邏輯要寫在上面給用戶訪問的 url 裏的回調的頁面裏。

請求 url 是:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

APPID 和 secret 去公衆號平臺 - > 開發 -> 基本配置 裏找。

code 是返回的 code

grant_type 就寫 authorization_code

這裏須要使用 http 客戶端庫,例如 python3 的 requests 或者 python2 的 urllib2,請求微信的接口

python3 的例子:

import requests
APPID = ''
secret = ''

r = requests.get('https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code')

請求結果是:

{
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200,
  "refresh_token":"REFRESH_TOKEN",
  "openid":"OPENID",
  "scope":"SCOPE" 
}

這時候須要再發起一次請求才能獲得用戶的信息。

此次的請求 url:

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

這個返回的結果是:

{   
  "openid":" OPENID",
  "nickname": NICKNAME,
  "sex":"1",
  "province":"PROVINCE",
  "city":"CITY",
  "country":"COUNTRY",
  "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

最終的 view 是:

def weixinbind_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')
    # print(code, state)
    APPID = 'xxxxxx'
    secret = 'xxxxx'
    r = requests.get(
        'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code' % (APPID, secret, code)
    )
    print(r.text)
    d = json.loads(r.text)
    # {
    #   "access_token":"ACCESS_TOKEN",
    #   "expires_in":7200,
    #   "refresh_token":"REFRESH_TOKEN",
    #   "openid":"OPENID",
    #   "scope":"SCOPE"
    # }
    r = request.get(
        'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN' % (d['access_token'], d['openid'])
    )
    # print(r.text)
    dd = json.load(r.text)
    # {
    #   "openid":" OPENID",
    #   "nickname": NICKNAME,
    #   "sex":"1",
    #   "province":"PROVINCE",
    #   "city":"CITY",
    #   "country":"COUNTRY",
    #   "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
    #   "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
    #   "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
    # }
    request.user.userprofile.openid = dd['openid']
    request.user.userprofile.nickname = dd['nickname']
    request.user.userprofile.wsex = dd['sex']
    request.user.userprofile.province = dd['province']
    request.user.userprofile.city = dd['city']
    request.user.userprofile.country = dd['country']
    request.user.userprofile.headimgurl = dd['headimgurl']
    request.user.userprofile.privilege = dd['privilege']
    request.user.userprofile.unionid = dd['unionid']
    request.user.userprofile.save()
    return HttpResponseRedirect('/weixin/')

添加回調域名

網頁受權域名

到公衆號平臺 設置-> 公衆號設置 -> 功能設置 -> 網頁受權域名 的配置選項中,把剛剛的回調的 url 的域名添加上,有一個認證文件。

他說要把文件傳到服務器上,可是這個對於 django 不太方便,能夠直接寫一個 view 返回這個文件內容就能夠了,我直接放到 urls 文件裏了。

from django.urls import path
from django.http import HttpResponse

def weixin_verify(request):
    return HttpResponse('PJRLUusp1NXyuD70')



urlpatterns = [
    # 微信開發
    path('weixin/', account.views.weixin_page),
    path('weixin/bind/', account.views.weixinbind),
    path('weixin/bind/callback/', account.views.weixinbind_callback),
    path('MP_verify_PJRLUusp1NXyuD70.txt', weixin_verify),


]

最終代碼總覽

urls.py

from django.urls import path
from django.http import HttpResponse

def weixin_verify(request):
    return HttpResponse('PJRLUusp1NXyuD70')

urlpatterns = [
    # 微信開發
    path('weixin/', account.views.weixin_page),
    path('weixin/bind/', account.views.weixinbind),
    path('weixin/bind/callback/', account.views.weixinbind_callback),
    path('MP_verify_PJRLUusp1NXyuD70.txt', weixin_verify),


]

views.py

import requests
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
import time
import json


def weixin_page(request):
    return render(request, 'weixin_index.html')


def weixinbind(request):
    return HttpResponseRedirect('https://open.weixin.qq.com/connect/oauth2/authorize?appid=XXXXX&redirect_uri=https%3A%2F%2FXXXXX.com%2Fweixin%2Fbind%2Fcallback%2F&response_type=code&scope=snsapi_userinfo#wechat_redirect')


def weixinbind_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')
    print(code, state)
    APPID = 'xxxxx'
    secret = 'xxxxx'
    r = requests.get(
        'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code' % (APPID, secret, code)
    )
    print(r.text)
    d = json.loads(r.text)
    # {
    #   "access_token":"ACCESS_TOKEN",
    #   "expires_in":7200,
    #   "refresh_token":"REFRESH_TOKEN",
    #   "openid":"OPENID",
    #   "scope":"SCOPE"
    # }
    r = requests.get(
        'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN' % (d['access_token'], d['openid'])
    )
    print(r.content)
    dd = json.loads(r.content.decode('utf8'))
    # {
    #   "openid":" OPENID",
    #   "nickname": NICKNAME,
    #   "sex":"1",
    #   "province":"PROVINCE",
    #   "city":"CITY",
    #   "country":"COUNTRY",
    #   "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
    #   "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
    #   "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
    # }
    request.user.userprofile.openid = dd['openid']
    request.user.userprofile.nickname = dd['nickname']
    request.user.userprofile.wsex = dd['sex']
    request.user.userprofile.province = dd['province']
    request.user.userprofile.city = dd['city']
    request.user.userprofile.country = dd['country']
    request.user.userprofile.headimgurl = dd['headimgurl']
    request.user.userprofile.privilege = json.dumps(dd['privilege'])
    #request.user.userprofile.unionid = dd['unionid']
    request.user.userprofile.refresh_token_time = time.time()
    request.user.userprofile.save()
    return HttpResponseRedirect('/weixin/')

models.py

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, default=None)
    # 微信開發
    openid = models.CharField("微信 openid", max_length=32, default='')
    nickname = models.CharField("微信暱稱", max_length=256, default='')
    wsex = models.CharField("微信性別", max_length=3, default='')
    province = models.CharField("微信省份", max_length=50, default='')
    city = models.CharField("微信城市", max_length=50, default='')
    country = models.CharField("微信國家", max_length=50, default='')
    headimgurl = models.CharField("微信頭像", max_length=200, default='')
    privilege = models.CharField("微信權限", max_length=3, default='')
    unionid = models.CharField("微信 unionid", max_length=32, default='')
    refresh_token = models.CharField("微信 refresh_token", max_length=512, default='')
    refresh_token_time = models.IntegerField(default=0)

html 部分省略了

錯誤解決

微信登陸失敗 redirect_uri域名與後臺配置不一致,錯誤碼10003

到公衆號平臺 設置-> 公衆號設置 -> 功能設置 -> 網頁受權域名 的配置選項中,修改受權回調域名。

見上文 添加回調域名。

錯誤

歡迎來到個人博客:https://codeplot.top/
技術分類文章

相關文章
相關標籤/搜索