微信小程序開發5 后端解析wx.getUserInfor中的用戶信息, 微信小程序支付


后端,如何解析wx.getUserInfor中的用戶信息。

1 我們用encryptedDataiv,進行解密,必須要用到session_key,所以用必須是登入狀態。
2 但是session_key是有有效期。而且session_key的有效期,不是一個固定值,他是通過用戶行為來決定,session_key的有效期時間。
3 但是我們可以通過wx.checkSession來判斷有沒有過期。
4 保證session_key沒有過期的情況下。我們將iv,encryptedData,token(登入憑證)發送到后端.
5 后端使用官方提供的sdk,進行解密。
地址 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
6 解密成功以后保存到數據,數據庫的字符集一定要是utf8mb4,因為utf8默認是3字節,微信這類帶表情包名字必須4字節才能保存表情包

沒有敏感信息,無法獲得用戶信息

 

可以得到用戶信息

 

 數據庫utf8mb4:

當要用到微信名字,表情包時,創建數據庫設為utf8mb4格式

 

 django中settings文件配置

import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'python13',
        'USER':'root',
        'PASSWORD':'',
        'HOST':'127.0.0.1',
        'PORT': 3306,
        'OPTIONS': {'charset': 'utf8mb4'},  # 設為4字節,兼容符號
    }
}

如官方的sdk沒有Crypto包用下面的方法解決:

pip install pycryptodome 

 

代碼示例:

微信小程序前端:

app.js

//app.js
App({
  /*
  當小程序初始化完成,會觸發onlaunch(全局只觸發一次)
   */
  onLaunch: function () { 
    // 登錄
    this.my_login()
    //this 就是當前app對象
  },
  my_login:function(){
    let that = this
    wx.login({
      success: res => {
        // 發送 res.code 到后台換取 openId, sessionKey, unionId
        console.log(res.code)
        wx.request({
          url: that.globalData.baseurl + 'login/',
          data: { 'code': res.code },
          method: "POST",
          success(e) {
            wx.setStorageSync('token', e.data.data.token)
          }
        })
      }
    })

    console.log("小程序的初始化:onlaunch")
  },
  /**可以在全局使用 */
  globalData: {
    userInfo: null,
    baseurl:"http://127.0.0.1:8000/"
  }
})

test3.wxml

<button open-type="getUserInfo" bindgetuserinfo='user1'>用戶信息</button>

test3.js

// pages/test3/test3.js
const app = getApp()  // 引用app模塊

Page({
  user1:function(e){
    // this 當前頁面,也就是page對象
    // console.log('e', e.detail) // 等同於wx.getUserInfo方法
    wx.getSetting({
      success(res) {
        if (res.authSetting['scope.userInfo']) { // 獲取用戶信息權限

          wx.checkSession({  // 確認用戶session_key有無過期
            success() {
              //session_key 未過期,並且在本生命周期一直有效
              wx.getUserInfo({
                success: (res) => {
                  console.log('res', res)  // 這個res里面就是用戶信息
                  // 將數據發送到后端
                  wx.request({
                    // 這里是發送iv,encryptedData
                    url: app.globalData.baseurl+'getinfo/',
                    data:{
                      iv:res.iv,
                      encryptedData:res.encryptedData,
                      token: wx.getStorageSync('token')
                    },
                    method:"POST",
                    success:(e) => {
                      console.log('后台返回的數據',e)
                    }
                  })
                }
              })
            },
            fail() {
              // session_key 已經失效,需要重新執行登錄流程
              app.my_login()  // 通過開頭導入const app = getApp(),獲取app對象

              wx.getUserInfo({
                success: (res) => {
                  console.log('res', res)  // 這個res里面就是用戶信息
                  ////這里是發送iv,encryptedData,還沒寫
                  wx.request({
                    url: 'url',
                  })
                }
              })
            }
          })
        } 
      }
    })
  },
})

django后端:

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01.views import test,user,pay

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^getinfo/', user.Info.as_view()),
    url(r'^pay/',pay.Pay.as_view())
]

views/user.py

from rest_framework.views import APIView
from  rest_framework.response import  Response
from app01.wx import  wx_login
from django.core.cache import cache
import hashlib,time
from app01.models import Wxuser
from app01.wx import WXBizDataCrypt
from app01.my_ser import wx_user_ser

class Login(APIView):
    def post(self,request):
        param = request.data
        if not param.get("code"):
            return Response({"status":1,"msg":"缺少參數"})
        else:
            code = param.get("code")
            user_data = wx_login.get_login_info(code)
            if user_data:
               val = user_data['session_key'] +"&"+user_data['openid']
               md5 = hashlib.md5()
               md5.update(str(time.clock()).encode("utf-8"))    # 當前cpu的時間
               md5.update(user_data['session_key'].encode("utf-8"))
               key = md5.hexdigest()
               cache.set(key,val,7200)
               has_user = Wxuser.objects.filter(openid=user_data['openid']).first()
               if not has_user:
                   Wxuser.objects.create(openid=user_data['openid'])
               return Response({
                   "status":0,
                   "msg":"ok",
                   "data":{"token":key}
               })
            else:
                return  Response({"status":2,"msg":"無效的code"})


class Info(APIView):
    def post(self,request):
        param = request.data
        if param.get('iv') and param.get('token') and param.get('encryptedData'):
            session_key_openid = cache.get(param.get('token'))

            if session_key_openid:
                session_key,openid = session_key_openid.split('&')

                # 解密
                user_info = WXBizDataCrypt.WXBizDataCrypt.get_info(session_key,param.get('encryptedData'),param.get('iv'))

                save_data = {
                    "name": user_info['nickName'],
                    "avatar": user_info['avatarUrl'],
                    "language": user_info['language'],
                    "province": user_info['province'],
                    "city": user_info['city'],
                    "country": user_info['country'],
                }
                Wxuser.objects.filter(openid=openid).update(**save_data)
                user = Wxuser.objects.filter(openid=openid).first()
                user = wx_user_ser(instance=user,many=False).data

                return Response({
                    'status':0,
                    'msg':'ok',
                    'data':user
                })


            else:
                return Response({'code':2,'msg':'無效的token'})
        else:
            return Response({'code':1,'msg':'缺少參數'})

wx/settings.py

AppId="..."

AppSecret='...'

code2Session = 'https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code'

pay_mchid ='1415981402'
pay_apikey = 'xi34nu5jn7x2uujd8u4jiijd2u5d6j8e'

wx/wx_login.py

from  app01.wx import settings
import requests

def get_login_info(code):
    code_url = settings.code2Session.format(settings.AppId,settings.AppSecret,code)
    response = requests.get(code_url)
    json_response = response.json() # 把json格式數據轉換為字典
    print("json_response",json_response)
    if json_response.get("session_key"):
        return json_response
    else:
        return  False

wx/WXBizDataCrypt.py

import base64
import json
from Crypto.Cipher import AES
from app01.wx import settings

class WXBizDataCrypt:
    def __init__(self, appId, sessionKey):
        self.appId = appId
        self.sessionKey = sessionKey

    def decrypt(self, encryptedData, iv):
        # base64 decode
        sessionKey = base64.b64decode(self.sessionKey)
        encryptedData = base64.b64decode(encryptedData)
        iv = base64.b64decode(iv)

        cipher = AES.new(sessionKey, AES.MODE_CBC, iv)

        decrypted = json.loads(self._unpad(cipher.decrypt(encryptedData)))

        if decrypted['watermark']['appid'] != self.appId:
            raise Exception('Invalid Buffer')

        return decrypted

    def _unpad(self, s):
        return s[:-ord(s[len(s)-1:])]

    @classmethod
    def get_info(cls,sessionKey,encryptedData,iv):

        return cls(settings.AppId, sessionKey).decrypt(encryptedData, iv)

models.py

from django.db import models

# Create your models here.
class Wxuser(models.Model):
    id = models.AutoField(primary_key=True)
    openid=models.CharField(max_length=255)
    name = models.CharField(max_length=50)
    avatar = models.CharField(max_length=200)
    language = models.CharField(max_length=50)
    province = models.CharField(max_length=50)
    city = models.CharField(max_length=50)
    country = models.CharField(max_length=50)
    #gender = models.CharField(max_length=50)
    creat_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)
    def __str__(self):
        return self.openid

my_ser.py

from rest_framework import serializers
from app01 import models

class wx_user_ser(serializers.ModelSerializer):
    class Meta:
        model = models.Wxuser
        fields = "__all__"

 

小程序支付流程

1 用戶發起請求下單支付
2 我們要保證用是登入狀態。
3 組織數據,請求統一下單接口,微信官方會同步返回一個prepay_id
4 重新組織數據,進行簽名,將重新組織的數據返回給小程序,小程序在吊起支付。
5 用戶就可以進行支付,支付結果會同步返回給小程序
6 后台修改訂單支付狀態是通過微信官方服務器的異步通知

 

xml解析模塊

<xml>
  <appid name="屬性值" >{.child.text}</appid>
   child.tag表示appid   
</xml> 

import xml.etree.ElementTree as ET

如果我們要解析一個xml文件
tree = ET.parse('country_data.xml')
root = tree.getroot()

如果解析字符串
root = ET.fromstring(country_data_as_string)

這個root是 Element 
for child in root:
     print(child.tag, child.attrib)
     #child.tag表是標簽名,child.attrib表示獲取屬性
     #child.text就表示獲取內容

 

代碼示例:

微信前端

test3.wxml

<button bindtap="pay" >下單支付</button>

test3.js

// pages/test3/test3.js
const app = getApp()
Page({

  /**
   * 頁面的初始數據
   */
  data: {

  },
  user1:function (e) {
    //this當前頁面page,對象
   // console.log('e', e.detail) // 等同於wx.getUserInfo方法
    wx.getSetting({
      success(res) {
        if (res.authSetting['scope.userInfo']) {  // 獲取用戶信息權限
          wx.checkSession({
            success () {
              //session_key 未過期,並且在本生命周期一直有效
              wx.getUserInfo({
                success: (res) => {
                  console.log("res",res)//這個res里面就是用戶信息
                  //將數據發送后端
                  wx.request({
                    //這里是發送iv,encryptedData,還沒寫
                    url: app.globalData.baseurl+"getinfo/",
                    data:{
                     iv:res.iv,
                     encryptedData:res.encryptedData,
                     token: wx.getStorageSync('token')
                    },
                    method:"POST",
                    success: (e) => {
                        console.log("后台返回的數據",e)
                    }

                  })
                },
              })

            },
            fail () {
              // session_key 已經失效,需要重新執行登錄流程
              app.my_login()

              wx.getUserInfo({
                success: (res) => {
                  console.log("res",res)//這個res里面就是用戶信息
                  ////這里是發送iv,encryptedData,還沒寫
                  wx.request({
                    url: 'url',
                  })
                },
              })
            }
          })
        }
      }
    })
    
  },
  pay:function(){
    wx.request({
      url: app.globalData.baseurl+"pay/",
      data:{"money":1,token:wx.getStorageSync('token')},
      method:"POST",
      success (e) {
        console.log("支付數據",e)
        wx.requestPayment(
          {
          'timeStamp':e.data.data.timeStamp,
          'nonceStr': e.data.data.nonceStr,
          'package': e.data.data.package,
          'signType': e.data.data.signType,
          'paySign': e.data.data.paySign,
          'success':function(res){
            console.log("成功",res)
          },
          'fail':function(res){
            console.log("失敗",res)
          },
          
          })
      }

    })
  }
})
View Code

 

django后端:

views/pay.py

from rest_framework.views import  APIView
from rest_framework.response import  Response
from django.core.cache import cache
from app01.wx import settings
import hashlib,requests,time

class Pay(APIView):
    def post(self,request):
        param = request.data
        if param.get("token") and param.get("money"):
            openid_session_key = cache.get(param.get("token"))
            if openid_session_key:
                # 獲取客戶端ip,如果是負載均衡,就用HTTP_X_FORWARDED_FOR,如果不是就用下面的
                # nginx 轉發:--》訪問是nginx,->nginx -> uwsgi
                if request.META.get('HTTP_X_FORWARDED_FOR'):
                    #有負載均衡就用這個
                    self.ip = request.META['HTTP_X_FORWARDED_FOR']
                else:
                    #沒有負載均衡就用這個
                    self.ip = request.META['REMOTE_ADDR']
                self.openid =openid_session_key.split("&")[1]
                self.money =param.get("money")
                data = self.get_pay_data()
                return  Response({"code":0,"msg":"ok","data":data})

            else:
                return Response({"code": 2, "msg": "token無效"})
        else:
            return  Response({"code":1,"msg":"缺少參數"})

    def get_nonce_str(self):
        import random
        data = "123456789abcdefghijklmn"
        nonce_str = "".join(random.sample(data,10))
        #random.sample(從哪里取,取多小個),變成列表
        return  nonce_str

    def get_order_id(self):
        import time
        import random
        data = "123456789abcdefghijklmn"
        order_no = str(time.strftime("%Y%m%d%H%M%S"))+"".join(random.sample(data, 5))
        return  order_no

    def get_sign(self):
        data_dict ={
        "appid" :  self.appid,
        "mch_id":self.mch_id,
        "nonce_str" : self.nonce_str,
        "body" :  self.body,
        "out_trade_no" : self.out_trade_no,
        "total_fee" : self.total_fee,
        "spbill_create_ip" : self.ip,
        "notify_url" : self.notify_url,
        "trade_type" :  self.trade_type,
        "openid" : self.openid,
        }
        sign_str = "&".join([f"{k}={data_dict[k]}"  for k in sorted(data_dict)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        print("sign_str", sign_str)
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return  sign.upper()


    def xml_to_dict(self,xml_data):
        import xml.etree.ElementTree as ET

        xml_dict ={}

        root = ET.fromstring(xml_data)

        for child in root:
            xml_dict[child.tag]= child.text
        return  xml_dict



    def get_two_sign(self,data):
        data_dict = {
            "appId":settings.AppId,
            "timeStamp":str(int(time.time())),  # 根據文檔要求整型樣子的字符串格式
            "nonceStr":data['nonce_str'],
            "package":f"prepay_id={data['prepay_id']}",
            "signType":"MD5"

        }
        sign_str = "&".join([f"{k}={data_dict[k]}" for k in sorted(data_dict)])
        sign_str = f"{sign_str}&key={settings.pay_apikey}"
        md5 = hashlib.md5()
        md5.update(sign_str.encode("utf-8"))
        sign = md5.hexdigest()
        return sign.upper() , data_dict['timeStamp']



    def get_pay_data(self): # 參考 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1
        self.appid = settings.AppId
        self.mch_id = settings.pay_mchid
        self.nonce_str = self.get_nonce_str()
        self.body = "老男孩學費"
        self.out_trade_no = self.get_order_id()
        self.total_fee =self.money
        self.spbill_create_ip =self.ip      # 調用微信支付API的機器IP
        self.notify_url = "htttp://www.test.com"    # 異步接收微信支付結果通知的回調地址
        self.trade_type ="JSAPI"
        self.openid = self.openid       # 用戶id
        self.sign = self.get_sign()

        body_data = f'''
        <xml>
            <appid>{self.appid}</appid>
            <mch_id>{self.mch_id}</mch_id>
            <nonce_str>{self.nonce_str}</nonce_str>
            <body>{self.body}</body>
            <out_trade_no>{self.out_trade_no}</out_trade_no>
            <total_fee>{self.total_fee}</total_fee>
            <spbill_create_ip>{self.spbill_create_ip}</spbill_create_ip>
            <notify_url>{self.notify_url}</notify_url>
            <trade_type>{self.trade_type }</trade_type>
            <openid>{self.openid }</openid>      
            <sign>{self.sign}</sign>      
        </xml> 
        '''
        url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
        # 如果發送的xml數據要把數據轉化二進制。body_data.encode("utf-8")
        # request
        response = requests.post(url,data=body_data.encode("utf-8"),headers = {"content-type":"application/xml"} )
        #接收一個二進制的響應
        data_dict = self.xml_to_dict(response.content)
        pay_sign,timeStamp = self.get_two_sign(data_dict)   # 再次簽名
        data = {

            "timeStamp": timeStamp,
            "nonceStr": data_dict['nonce_str'],
            "package": f"prepay_id={data_dict['prepay_id']}",
            "signType": "MD5",
            "paySign":pay_sign
        }
        return data

其他代碼同上

 

小程序支付在總結

1 接收到支付請求。我們先組織數據,然后進行統一下單前的簽名
- 請求的數據與響應的數據都是xml.請求的時候,xml數據要變成二進制,heards中的content-type:"application/xml"
-響應的數據也是xml,我要用xml.etree.ElementTree將他轉化為字典
2 拿到統一下單數據,最重要的prepay_id,進行再次簽名。把一下數據發送給小程序。
            "timeStamp": 時間戳
            "nonceStr":隨機字符串
            "package": f"prepay_id={data_dict['prepay_id']}",統一下單中的到的prepay_id
            "signType": "MD5",
            "paySign":通過上面數據進行加密的結果
 3 小程序掉用wx.resquestPayment()吊起支付

 

以后再支付文檔

1 統一下單
簽名:80%。
簽名方式沒有搞懂。用了哪些數據,傳過去的數據,和簽名的數據不一致。
appid = 123 傳過去,appid =456
appid 123  --->apid

每一個數據的類型,長短,意義是什么?一定要搞清楚,
2異步回調

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM