• 微信小程序开发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异步回调
  • 相关阅读:
    在同一iphone项目添加lite版
    加权平均资本成本【读书笔记】
    强制iphone界面马上旋转
    市场营销经典《引爆点》简摘
    操作系统与固件,驱动是什么
    如何使用单元测试测试线程安全
    20192428魏来 《网络与系统系统攻防技术》实验四 恶意代码分析
    计网复习 https的加密过程
    jdk8下载安装与环境变量配置
    panic: qtls.ConnectionState not compatible with tls.ConnectionState
  • 原文地址:https://www.cnblogs.com/ludingchao/p/12493254.html
Copyright © 2020-2023  润新知