Python Flask 微信小程序点餐系统(三):小程序登录

架构

在这里插入图片描述

数据表

CREATE TABLE `member` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `nickname` varchar(100) NOT NULL DEFAULT '' COMMENT '会员名',
  `mobile` varchar(11) NOT NULL DEFAULT '' COMMENT '会员手机号码',
  `sex` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别 1:男 2:女',
  `avatar` varchar(200) NOT NULL DEFAULT '' COMMENT '会员头像',
  `salt` varchar(32) NOT NULL DEFAULT '' COMMENT '随机salt',
  `reg_ip` varchar(100) NOT NULL DEFAULT '' COMMENT '注册ip',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态 1:有效 0:无效',
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后一次更新时间',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '插入时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';

还有一张第三方绑定关系表,这个和其类似。

代码书写

首先我们可以去微信小程序的开发者平台将其接口文档研读一遍,大致清楚上面图的流程后就可以下载小程序开发工具,然后导入项目的路径进入:

点击导入后,查看项目设置里有没有我们的信息以及点击编译,没有错误的情况下就能看到如下三个图层:

然后我们就可以去开发我们的代码了,这里需要在index.wxml中更改按钮为微信提供的更安全的button:

/*--<view class="confirm-btn" bindtap='goToIndex'>
  <text >走吧,订餐去</text>
</view> */

<button class="confirm-btn"  open-type="getUserInfo" bindgetuserinfo="login">
    授权登录
</button>

微信授权后可提供的信息为:

// 必须是在用户已经授权的情况下调用
wx.getUserInfo({
  success: function(res) {
    var userInfo = res.userInfo
    var nickName = userInfo.nickName
    var avatarUrl = userInfo.avatarUrl
    var gender = userInfo.gender //性别 0:未知、1:男、2:女
    var province = userInfo.province
    var city = userInfo.city
    var country = userInfo.country
  }
})

如果需要获取到上述信息,我们可以在index.js里直接打印e。

  onReady: function(){...},
  login:function (e) {
      app.console(e);
  }

然后在开发者工具上点击授权登录就能看到相关信息:

那么和上一篇不同的是,这里就需要前端来写主要的登录和校验功能,而后端需要先将前端传过来的信息存进数据库里才能做一些校验,还有统一拦截器的修改,我们接下来往下看。

这里还要做一些额外的配置,如果小程序前端要和我们的后端连接,在设置更改里勾选上不校验合法域名:

以及更改一下app.js文件中的globaldata:

    globalData: {
        userInfo: null,
        version: "1.0",
        shopName: "Python3 + Flask 订餐全栈系统",
        domain:"http://127.0.0.1:5000/api"	// 这是我本机的地址
    },

然后我们的前端代码为:

login:function( e ){
        var that = this;
        if( !e.detail.userInfo ){
            app.alert( { 'content':'登录失败,请再次点击~~' } );
            return;
        }

        var data = e.detail.userInfo;	// 微信小程序固定写法
        wx.login({
            success:function( res ){
                if( !res.code ){
                    app.alert( { 'content':'登录失败,请再次点击~~' } );
                    return;
                }
                data['code'] = res.code;
                wx.request({
                    url:app.buildUrl( '/member/login' ),	// 本来应该是127.0.0.1:5000/member/login,但我们可以将上节构造的url处理器做简化
                    header:app.getRequestHeader(),      // 将json替换成表单:'content-type': 'application/x-www-form-urlencoded',
                    method:'POST',
                    data:data,
                    success:function( res ){
                        if( res.data.code != 200 ){
                            app.alert( { 'content':res.data.msg } );
                            return;
                        }
                        // app.setCache( "token",res.data.data.token );
                        that.goToIndex();       // 跳至首页
                    }
                });
            }
        });
    }

于是就可以看到小程序端的表单信息:

在这里插入图片描述

关于后端代码,首先注册蓝图:

from web.controllers.api import route_api

app.register_blueprint(route_api,url_prefix="/api")

我们在controller目录下的api包下的init文件定义我们的蓝图以及其它信息,因为这个文件夹下的文件会很多,所以全部写入init文件统一管理:

# -*- coding: utf-8 -*-
from flask import Blueprint
route_api = Blueprint( 'api_page',__name__ )
from web.controllers.api.Member import *
from web.controllers.api.Food import *
from web.controllers.api.Order import *
from web.controllers.api.My import *
from web.controllers.api.Cart import *
from web.controllers.api.Address import *

@route_api.route("/")
def index():
    return "Mina Api V1.0~~"

我们可以将项目启动测试一下是否能访问到这个路由,会出现Mina Api V1.0~~,没有问题的话就可以写api.Member代码了。
在这里插入图片描述

Member代码为:

@route_api.route("/member/login",methods=["POST","GET"])
def login():
    resp = {"code":200,"msg":"操作成功","data":{}}
    req = request.values
    code = req["code"] if "code" in req else ""
    # 前端点击登录时获取的 code,必须通过后端拿到openid唯一标识
    if not code or len(code) < 1:
        resp["code"] = -1
        resp["msg"] = "需要code"
        return jsonify(resp)

    # todo 前端传过来的code通过后端获取到openid,方法进行了封装
    openid = MemberService.getWeChatOpenId(code)
    if openid is None:
        resp["code"] = -1
        resp["msg"] = "调用微信接口出错"
        return jsonify(resp)

    nickname = req["nickName"] if "nickName" in req else ""
    sex = req["gender"] if "gender" in req else 0
    avatar = req["avatarUrl"] if "avatarUrl" in req else ""
    '''
        判断是否已经测试过,注册了直接返回一些信息
    '''
    bind_info = OauthMemberBind.query.filter_by(openid=openid,type=1).first()
    if not bind_info:   # 如果数据库没有就进行统一注册
        model_member = Member()
        model_member.sex = sex
        model_member.avatar = avatar
        model_member.salt = MemberService.geneSalt()    # todo 生成加盐字段,为了之后的登录加密
        model_member.updated_time = model_member.created_time = getCurrentDate()
        db.session.add(model_member)
        db.session.commit()

        model_bind = OauthMemberBind()
        model_bind.member_id = model_member.id
        model_bind.type = 1
        model_bind.openid = openid
        model_bind.updated_time = model_bind.created_time = getCurrentDate()	# todo 创建相应的日期
        db.session.add(model_bind)
        db.session.commit()

        bind_info = model_bind

    # member_info = Member.query.fitler_by(id = bind_info.member_id).first()
    return jsonify(resp)

其中上面有三个TODO的地方,其中openid是需要我们通过前端传来的code还有一系列信息对小程序端的某个api进行请求,具体的信息我们可以看如下文档:

auth.code2Session

我们在MemberService中,大致代码为:

class MemberService():

    @staticmethod
    def geneAuthCode(member_info=None):
        m = hashlib.md5()
        str = "%s-%s-%s" % (member_info.id,member_info.salt,member_info.status)
        m.update(str.encode("utf-8"))
        return m.hexdigest()

    @staticmethod
    def geneSalt(length=16):
        keylist = [random.choice((string.ascii_letters + string.digits)) for i in range(length)]
        # 产生16位随机的数字与字母组成的列表,然后用join拼接成字符串
        return ("".join(keylist))

    @staticmethod
    def getWeChatOpenId( code ):
        url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code" \
            .format(app.config['MINA_APP']['appid'], app.config['MINA_APP']['appkey'], code)
        """
        appid:小程序 appId
        secret:小程序 appSecret
        js_code:登录时获取的 code,前端传过来的code
        """
        r = requests.get(url)   # 根据官网的要求需要发送get请求,获取到响应对象
        res = json.loads(r.text)    # 从中取出内容
        openid = None   # openid:用户唯一标识
        if 'openid' in res:
            openid = res['openid']
        return openid   # 将其当成变量返回

然后这里还会有问题,因为我们之前在后台项目中定义了全局拦截器,当我们点击小程序登录的时候会发现后端的日志会一直在302重定向,所以我们要去settings里面把"/api"也忽略掉,然后就能看到如下的图了:

然后我们就可以从数据库中的member表和oauth_member_bind表中看到我们插入的记录:
在这里插入图片描述

优化

我们可以想一个问题,假如我们已经注册过了之后,我们的信息显示是不是可以更换,即我们可以不需要也不必要一直去调用授权的方法,我们可以在第一步的时候可以检验是否有token,而让代码更加高效:

所以我们对后端来说加入检验登录的方法:

@route_api.route("/member/check-reg",methods = [ "GET","POST" ])
def checkReg():
    resp = {'code': 200, 'msg': '操作成功~', 'data': {}}
    req = request.values
    code = req['code'] if 'code' in req else ''
    if not code or len(code) < 1:
        resp['code'] = -1
        resp['msg'] = "需要code"
        return jsonify(resp)

    openid = MemberService.getWeChatOpenId(code)
    if openid is None:
        resp['code'] = -1
        resp['msg'] = "调用微信出错"
        return jsonify(resp)

    bind_info = OauthMemberBind.query.filter_by(openid=openid, type=1).first()
    if not bind_info:
        resp['code'] = -1
        resp['msg'] = "未绑定"
        return jsonify(resp)

    member_info = Member.query.filter_by( id = bind_info.member_id).first()
    if not member_info:
        resp['code'] = -1
        resp['msg'] = "未查询到绑定信息"
        return jsonify(resp)

    token = "%s#%s"%( MemberService.geneAuthCode( member_info ),member_info.id )
    resp['data'] = { 'token':token }
    return jsonify(resp)

而小程序部分,html部分的登录按钮用if判断更改为:

<view class="confirm-btn" bindtap='goToIndex' wx:if="{{regFlag==true}}">
          <text>走,逛逛去</text>
        </view>

<button class="confirm-btn"  open-type="getUserInfo" bindgetuserinfo="login" wx:if="{{regFlag==false}}">
            授权登录
        </button>

再加上checklogin.js:

    checkLogin:function(){
        var that = this;
         wx.login({
             success:function( res ){
                 if( !res.code ){
                    app.alert( { 'content':'登录失败,请再次点击~~' } );
                    return;
                 }
                 wx.request({
                    url:app.buildUrl( '/member/check-reg' ),
                    header:app.getRequestHeader(),
                    method:'POST',
                    data:{ code:res.code }, // 小程序只能后端做验证
                    success:function( res ){
                        if( res.data.code != 200 ){
                            that.setData({
                                regFlag:false
                            });
                            return;
                        }

                        app.setCache( "token",res.data.data.token );
                        //that.goToIndex();
                    }
                });
             }
         });
    }

那么最终的演示效果为:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/submarineas/article/details/103569946