【理论+实战】Django会话管理+微信小程序

Django会话管理

会话管理&有状态服务:
1.什么是有状态服务
  1.1 有状态服务
  1.2 真实请求讲解
  1.3 小程序中的状态
2.实现登陆功能
  2.1 用户体系的建立
  2.2 授权登陆功能的实现
3.如何实现有状态服务
  3.1 小程序中的状态管理
  3.2 复杂多变的用户状态管理
4.有状态服务的效果
  4.1 有状态的首页实现
5.完整代码

1.什么是有状态服务
1.1 有状态服务
1.1.1 什么是状态?
(有状态服务与无状态服务)
评判标准:上下文关系,有状态服务的凭证
1.1.2 HTTP协议中的状态
在这里插入图片描述
1.客户端发起一个空请求,服务端将请求的相关数据(上下文)保存并标记Session(凭证)。服务端将请求进行返回,客户端拿到Session后,记录到Cookie里面
2.客户端发起一个请求(带Cookie),服务端拿到Cookie后搜索该凭证的相关数据,并将其返回。

1.2 真实请求讲解:
1.2.1 首次请求: 没有携带任何Cookie,服务端返回set-Cookie,叫浏览器设置Cookie,下次请求将携带该Cooikie如下图所示。
在这里插入图片描述
1.2.2 尝试进行登陆请求: 最顶的红框是登陆页面,登陆完成后跳转首页;服务端返回set-cookie,叫浏览器设置凭证,接下来的跳转首页将携带该凭证(Cookie)。
在这里插入图片描述
1.2.3 跳转到首页: 可以查看到登陆后跳转到首页的请求携带了1.2.2的相关凭证(Cookie)
在这里插入图片描述
1.3 小程序中的状态
在这里插入图片描述
如上图所示,在小程序有状态服务器开发中,不能套用传统的cookie和session模型进行有状态服务的开发。
小程序和微信服务器不存在上下文关系
微信服务器和Django后台不存在上下文关系
小程序和Django后台不存在直接上下文关系
如何解决呢?
由于微信服务器只对请求和响应进行转发,没有其他的数据处理和保存等,所以可以在小程序对cookie进行处理,同样服务端对session进行处理。具体看3.1节。
2.小程序的登陆功能
2.1 建立用户体系
2.1.1 自建用户体系VS沿用微信用户体系
自建用户体系:邮箱、手机号标记用户;需实现填写、去重等复杂的交互逻辑,需要进行注册、登陆等功能的开发;
沿用微信用户体系:openid
2.1.2 用户模型与用户视图
用户的唯一标识:openid
用户的基本信息:nickname
authorization/models.py

from django.db import models

# Create your models here.
class User(models.Model):
    open_id = models.CharField(max_length=32, unique=True)  # 微信的openid是长度为32的字符串
    nickname = models.CharField(max_length=256)  # 用户名

python manage.py makemigrations # 检测数据模型的变化,若有则生成文件来记录
python manage.py migrate #运行所有生成出来的文件来对数据库进行修改
authorization/views.py

from django.view import View

class UserView(View):
    def get(self, request):
        pass

    def post(self, request):
        pass

2.1.3 获取用户的OpenID
在这里插入图片描述
2.2 授权登陆功能的实现
小程序侧:
page/homepage.js

authorize: function(){
    wx.login({
      success: function(res){
        var code =res.code
        var appId = app.globalData.appId
        var nickname = app.globalData.userInfo.nickName
        wx.request({
          url: app.globalData.serverUrl + app.globalData.apiVersion + '/auth/authorize',
          method: 'POST',
          data: {
            code: code,
            appId: appId,
            nickname: nickname  
          },
          header: {'content-type': 'application/json'},
          success: function(res){
            wx.showToast({
              title: '授权成功',
            })
            var cookie = cookieUtil.getSessionIDFromResponse(res)
            cookieUtil.setCookieToStorage(cookie)
            console.log(cookie)
          }
        })
      }
    })
  },

django后台:

authorization/views.py

def c2s(appid, code):
    API = 'https://api.weixin.qq.com/sns/jscode2session'
    params = 'appid=%s&secret=%s&js_code=%s&grant_type=authorization_code' % \
             (appid, settings.WX_APP_SECRET, code)
    url = API + '?' + params
    response = requests.get(url=url)
    data = json.loads(response.text)
    print('data from weixin server:', data)
    return data
    
def __authorize_by_code(request):
    """
    使用wx.login到的临时code到微信提供的code2session接口授权

    post_data = {
        'encryptedData': 'xxx',
        'appId': 'xxx',
        'sessionKey': 'xxx',
        'iv': 'xxx'
    }
    """
    post_data = request.body.decode('utf-8')
    print('data from applet:', post_data)
    post_data = json.loads(post_data)
    code = post_data.get('code').strip()
    app_id = post_data.get('appId').strip()
    nickname = post_data.get('nickname').strip()
    response = {}
    # 需要微信小程序提供code和appid,然后将code、appid和secret提交给c2s向微信接口服务认证
    if not code or not app_id:
        response['message'] = 'lost code or appId'
        response['code'] = ReturnCode.BROKEN_AUTHORIZED_DATA
        return JsonResponse(data=response, safe=False)
    data = c2s(app_id, code)
    openid = data.get('openid')
    print('get openid:', openid)
    # 若没有从微信接口服务获取到openid,则认证失败
    if not openid:
        response = wrap_json_response(code=ReturnCode.UNAUTHORIZED, message='auth failed')
        return JsonResponse(data=response, safe=False)

    request.session['open_id'] = openid
    request.session['is_authorized'] = True

    # 认证成功后查看该用户是否在开发者数据存在,不存在则保存
    if not User.objects.filter(open_id=openid):
        new_user = User(open_id=openid, nickname=nickname)
        new_user.save()

    response = wrap_json_response(code=ReturnCode.SUCCESS, message='auth success.')
    return JsonResponse(data=response, safe=False)


def authorize(request):
    return __authorize_by_code(request)

3.如何实现有状态服务
3.1 小程序中的状态管理
3.1.1 小程序Storage存取Cookies
utils/cookies.js

const key = 'cookie'
//从response取Cookie
function getSessionIDFromResponse(res){
  var cookie = res.header['Set-Cookie']
  console.log('get cookie from response:' + cookie)
  return cookie
}
//将Cookie存储到Storage中
function setCookieToStorage(cookie){
  try{
    wx.setStorageSync(key,cookie)
  }catch(e){console.log(e)}
}
//从Storage中取Cookie
function getCookieFromStorage(){
  var value = wx.getStorageSync(key)
  return value
}
//导出即可在外部使用这三个函数
module.exports = {
  setCookieToStorage: setCookieToStorage,
  getCookieFromStorage: getCookieFromStorage,
  getSessionIDFromResponse: getSessionIDFromResponse
}

page/homepage.js

// pages/homepage/homepage.js

const app = getApp()
const cookieUtil = require('../../utils/cookie.js')

Page({
  /**
   * 页面的初始数据
   */
  data: {},
  onReadCookies: function(){
    wx.request({
      url: '127.0.0.1:8000/api/v1/auth/test',
      success: function(res){
        var cookie = cookieUtil.getSessionIDFromResponse(res)
        console.log(cookie)
        cookieUtil.setCookieToStorage(cookie)
        //二次带cookie访问
        var newCookie = cookieUtil.getCookieFromStorage()
        var header = {}
        header.Cookie = newCookie
        wx.request({
          url: '127.0.0.1:8000/api/v1/auth/test2',
          header: header,
          success: function(res){}
        })
      }
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {},
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {},
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {},
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {},
  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {},
  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {},
  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {},
  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {}
})

3.1.2 Django的Session中间件
在这里插入图片描述
启用Session中间件
settings.py

MIDDLEWARE = [
    ......
    'django.contrib.sessions.middleware.SessionMiddleware',
    ......  
]

使用Session的方法

from django.shortcuts import render
# Create your views here.
from django.http import JsonResponse
# 首此请求设置客户端cookie
def test_session(request):
    request.session['message'] = 'Test Django Session OK'
    return JsonResponse(data='{"result_code": 0, "message": "success"}', safe=False)
# 二次请求打印客户端携带的cookie
def test_session2(request):
    print(request.session.items())
    return JsonResponse(data='{"result_code": 0, "message": "success"}', safe=False)

3.2 复杂多变的用户状态管理
在这里插入图片描述
Django后台侧

def logout(request):
    request.session.clear()
    response = wrap_json_response(code=ReturnCode.SUCCESS, message='logout')
    return JsonResponse(data=response, safe=False)

设置session过期60秒:
settings.py

SESSION_COOKIE_AGE = 60*1

小程序侧
app.js

getAuthStatus: function(){
    return this.globalData.auth.isAuthorized
},
setAuthStatus: function(status){
    console.log('app.js/setAuthStatus: set auth status:', status)
    if (status == true || status == false){
      this.globalData.auth.isAuthorized = status
    }else{
      console.log('app.js/setAuthStatus: invalid status.')
    }
},

homepage.js

//登录
authorize: function () {
    console.log('authorize')
    var that = this
    // 登陆并获取cookie
    wx.login({
      success: function (res) {
        console.log(res)
        var code = res.code
        var appId = app.globalData.appId
        var nickname = app.globalData.userInfo.nickName
        // 请求后台
        wx.request({
          url: app.globalData.serverUrl + app.globalData.apiVersion + '/auth/authorize',
          method: 'POST',
          data: {
            code: code,
            appId: appId,
            nickname: nickname
          },
          header: {
            'content-type': 'application/json' // 默认值
          },
          success: function(res) {
            wx.showToast({
              title: '授权成功',
            })
            // 保存cookie
            var cookie = cookieUtil.getSessionIDFromResponse(res)
            cookieUtil.setCookieToStorage(cookie)
            that.setData({
              isLogin: true,
              userInfo: app.globalData.userInfo,
              hasUserInfo: true
            })
            app.setAuthStatus(true)
          }
        })
      }
    })
  },
//注销
logout: function(){
    var that = this
    var cookie = cookieUtil.getCookieFromStorage
    var header = {}
    header.Cookie = cookie
    wx.request({
      url: app.globalData.serverUrl+ app.globalData.apiVersion + '/auth/logout',
      method: 'GET',
      header: header,
      success: function(res){
        console.log('pages/homepage.js/logout: log', res)
        that.setData({
          isLogin: false,
          userInfo: null,
          hasUserInfo: false
        })
        cookieUtil.setCookieToStorage('')
        app.setAuthStatus(false)
      }
    })
  },

4.有状态服务的效果
4.1 有状态的首页实现
在这里插入图片描述
小程序侧:

//index.js
//获取应用实例
const app = getApp()
const cookieUtils = require('../../utils/cookie.js')

Page({
  data: {
    isAuthorized: false,
    constellationData: null,
    stockData: null,
    weatherData: null,
    userInfo: null,
    hasUserInfo: null
  },
  //事件处理函数
  bindViewTap: function() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  },
  updateData: function(){
    wx.showLoading({title: '加载中',})
    var that = this
    var cookie = cookieUtils.getCookieFromStorage()
    var header = {}
    header.Cookie = cookie
    wx.request({
      url: app.globalData.serverUrl + app.globalData.apiVersion + '/service/weather',
      method: 'GET',
      header: header,
      success: function(res){
        that.setData({weatherData: res.data.data})
        wx.hideLoading()
      }
    })
    wx.request({
      url: app.globalData.serverUrl + app.globalData.apiVersion + '/service/stock',
      method: 'GET',
      header: header,
      success: function(res){  
        that.setData({stockData: res.data.data})
        wx.hideLoading()
      }
    })
    wx.request({
      url: app.globalData.serverUrl + app.globalData.apiVersion + '/service/constellation',
      method: 'GET',
      header: header,
      success: function (res) {
        console.log(res)
        that.setData({constellationData: res.data.data})
        wx.hideLoading()
      }
    })
  },
  //下拉刷新,先检查session是否过期,再更新页面数据
  onPullDownRefresh: function(){
    var that = this
    var cookie = cookieUtils.getCookieFromStorage()
    var header = {}
    header.Cookie = cookie
    wx.request({
      url: app.globalData.serverUrl+app.globalData.apiVersion+'/auth/status',
      header: header,
      success: function(res){
        var data = res.data.data
        if (data.is_authorized ==1){
          that.setData({isAuthorized: true})
          that.updateData()
        }else{
          that.setData({isAuthorized: false})
          wx.showToast({
            title: '请先登陆',
            icon: 'none'
          })
        }
      }
    })
  },
  onLoad: function () {
    this.onPullDownRefresh()
    if (app.globalData.userInfo) {
      this.setData({
        userInfo: app.globalData.userInfo,
        hasUserInfo: true
      })
    } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
        success: res => {
          app.globalData.userInfo = res.userInfo
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        }
      })
    }
  },
  getUserInfo: function(e) {
    console.log(e)
    app.globalData.userInfo = e.detail.userInfo
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  },  
  teststorage: function(event){
    wx.setStorage({
      key: 'mykey',
      data: 'mydata',
    })
    wx.getStorage({
      key: 'mykey',
      success: function(res) {
        console.log(res.data)
      },
    })
  }
})

Django后台侧

# 获得个人设置的天气信息
class WeatherView(View, CommonResponseMixin, WeatherParse):  
    def get(self, request):
        if not already_authorized(request):
            response = self.wrap_json_response({}, code=ReturnCode.UNAUTHORIZED)
        else:
            data = []
            open_id = request.session.get('open_id')
            user = User.objects.filter(open_id=open_id)[0]
            cities = json.loads(user.focus_cities)
            for city in cities:
                city = re.sub(r'市', '', city['city'])
                print(city)
                result = self.get_weather_now(city)
                result['city_info'] = city
                data.append(result)
            response = self.wrap_json_response(data=data, code=ReturnCode.SUCCESS)
        return JsonResponse(data=response, safe=False)
        
popular_constellations = ['金牛座', '处女座', '天蝎座']
popular_stocks = [
    {
        'code': '000001',
        'name': '平安银行',
        'market': 'sz'
    },
    {
        'code': '600036',
        'name': '招商银行',
        'market': 'sh'
    },
    {
        'code': '601398',
        'name': '工商银行',
        'market': 'sh'
    }
]

# 星座
def constellation(request):
    if already_authorized(request):
        user = get_user(request)
        constellations = json.loads(user.focus_constellations)
    else:
        constellations = popular_constellations
    print(constellations)
    response = CommonResponseMixin.wrap_json_response(data=constellations, code=ReturnCode.SUCCESS)
    return JsonResponse(data=response, safe=False)

# 股票
def stock(request):
    if already_authorized(request):
        user = get_user(request)
        stock = json.loads(user.focus_stocks)
    else:
        stock = popular_stocks
    print(stock)
    response = CommonResponseMixin.wrap_json_response(data=stock, code=ReturnCode.SUCCESS)
    return JsonResponse(data=response, safe=False)

def already_authorized(request):
    if request.session.get('is_authorized'):
        return True
    else:
        return False

def get_user(request):
    if not already_authorized(request):
        raise Exception('not authorized request')
    open_id = request.session.get('open_id')
    user = User.objects.filter(open_id=open_id)[0]
    return user

5.完整代码
https://github.com/jstang007/wxapp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值