您当前的位置:首页 > 计算机 > 编程开发 > 小程序

小程序学习-用户使用手机登录验证

时间:09-13来源:作者:点击数:
CDSY,CDSY.XYZ

案例:用户使用手机号,输入验证码登录,验证码保存60s,用户登录成功后,获取用户信息。

小程序前台

app.js
App({

  /**
   * 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
   */
  onLaunch: function () {

    var userInfo = wx.getStorageSync('userInfo');
    if (userInfo) {
      this.globalData.userInfo = userInfo;
    }

  },
  globalData: {
    userInfo: null, // {phone:xxx,token:xxxx}
  },
  initUserInfo: function (res, localInfo) {
    var info = {
      token: res.token,
      phone: res.phone,
      nickName: localInfo.nickName,
      avatarUrl: localInfo.avatarUrl
    }
    // 1.去公共的app.js中调用globalData,在里面赋值。(在全局变量赋值)
    this.globalData.userInfo = info;//{phone:xxx,token:xxxx}

    // 2.在本地“cookie”中赋值
    wx.setStorageSync("userInfo", info);

  },
  delUserInfo: function () {
    this.globalData.userInfo = null;
    wx.removeStorageSync("userInfo")
  }
})

本地存储操作

wx.getStorageSync('userInfo');
wx.setStorageSync('userInfo',"yy");
wx.removeStorageSync("userInfo")
home页面

home.wxml

<view class="container">
  <view class="top-view">
    <view class="user">
      <view class="row">
        <image class="avatar" wx:if="{{userInfo}}" src="{{userInfo.avatarUrl}}"></image>
        <image class="avatar" wx:else="{{userInfo}}" src="/images/hg.jpg"></image>
        <view class="name" wx:if="{{userInfo}}">
          <view bindtap="onClickLogout">{{userInfo.nickName}}</view>
        </view>
        <view class="name" wx:else="{{userInfo}}">
          <navigator url="/pages/auth/auth">登录</navigator>
        </view>
      </view>
      <view class="site">查看个人主页</view>
    </view>
  </view>
</view>

home.wxss

.top-view{
  background-color: #01ccb6;
  color: white;
  padding: 40rpx;
}

.top-view .user{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}
.top-view .user .row{
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}
.top-view .user .avatar{
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
}

.top-view .user .name{
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  width: 200rpx;
}

.top-view .site{
  background-color: rgba(0, 0, 0, 0.16);
  padding: 20rpx;
  border-top-left-radius: 32rpx;
  border-bottom-left-radius: 32rpx;
}

home.js

var app = getApp();

Page({

  /**
   * 页面的初始数据
   */
  data: {
    userInfo: null
  },

  /**
   * 生命周期函数--监听页面加载(第一次打开时会执行)
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成(第一次打开时会执行)
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //本地storage中获取值
    this.setData({
      userInfo: app.globalData.userInfo
    })
  },
   /**
   * 用户注销
   */
  onClickLogout:function(){
    app.delUserInfo();
    this.setData({
      userInfo: null
    })
  },

})
auth页面

auth.wxml

<view class="form">
  <view class="row-group">
    <text>手机</text>
    <input placeholder="请填写手机号码" placeholder-class='txt' maxlength='11' value="{{phone}}" bindinput="bindPhoneInput" />
  </view>
  <view class="row-group">
    <text>验证码</text>
    <input placeholder="请填写验证码" placeholder-class='txt' maxlength='4' value="{{code}}" bindinput="bindCodeInput" />
    <view class="code" bindtap="onClickCheckCode">获取验证码</view>
  </view>

  <view>
    <button class="submit" open-type="getUserInfo" bindgetuserinfo="onClickSubmit">登录</button>
  </view>
</view>

auth.js

// pages/auth/auth.js

// 获取公共的那个app
var app = getApp();
Page({
  /**
   * 页面的初始数据
   */
  data: {
    phone:"",
    code:"",
  },
  bindPhoneInput: function (e) {
    this.setData({ phone: e.detail.value });
  },
  bindCodeInput: function (e) {
    this.setData({ code: e.detail.value });
  },
  /** 
   * 点击获取短信验证码
   */
  onClickCheckCode: function (e) {
    // 判断手机号格式是否正确
    if (this.data.phone.length == 0) {
      wx.showToast({
        title: '请填写手机号码',
        icon: 'none'
      })
      return
    }
    var reg = /^(1[3|4|5|6|7|8|9])\d{9}$/;
    if (!reg.test(this.data.phone)) {
      wx.showToast({
        title: '手机格式错误',
        icon: 'none'
      })
      return
    }
    // 发送短信验证码,登录成功之后获取jwt和微信用户信息,保存到globalData和本地存储中。
    wx.request({
      url: "http://127.0.0.1:8000/api/message/",
      data: { phone: this.data.phone },
      method: 'GET',
      dataType: 'json',
      success: function (res) {
        if(res.data.status){
          // 倒计时计数器
          wx.showToast({ title: res.data.message, icon: 'none' });
        }else{
          // 短信发送失败
          wx.showToast({title: res.data.message,icon: 'none'});
        }
      }
    })
    
  },
  onClickSubmit:function(e){
    e.detail.userInfo
    wx.request({
      url: "http://127.0.0.1:8000/api/login/",
      data: { phone: this.data.phone, code: this.data.code },
      method: 'POST',
      dataType: 'json',
      success: function (res) {
        if (res.data.status) {
          // 初始化用户信息
          app.initUserInfo(res.data.data, e.detail.userInfo);

          // var pages = getCurrentPages(); //获取所有page
          // prevPage = pages[pages.length-2];
          // 跳转会上一级页面
          wx.navigateBack({});
        } else {
          wx.showToast({ title: "登录失败", icon: 'none' });
        }
      }
    })
  },
})

auth.wxss

.logo{
  display: flex;
  flex-direction: column;
  align-items: center;
}

.logo image {
  margin-top: 140rpx;
  width: 216rpx;
  height: 100rpx;
}

.logo text {
  margin-top: 26rpx;
  margin-bottom: 50rpx;
  font-size: 24rpx;
  line-height: 24rpx;
  font-weight: 400;
  color: #8c8c8c;
  text-align: center;
}

.form{
  padding: 40rpx;
}

.form .row-group{
  padding: 20rpx 0;
  border-bottom: 1rpx solid #ddd;
  position: relative;
}

.form .row-group text{
  font-size: 28rpx;
  padding:10rpx 0;
}

.form .row-group input{
  padding: 10rpx 0;
}

.form .row-group .txt{
  color: #ccc;
}

.form .row-group .code{
  position: absolute;
  right: 0;
  bottom: 26rpx;
  z-index: 2;

  width: 206rpx;
  height: 60rpx;
  border: 2rpx solid #00c8b6;
  border-radius: 12rpx;
  font-size: 26rpx;
  font-weight: 400;
  color: #00c8b6;
  display: flex;
  align-items: center;
  justify-content: center;
}

.form .submit{
  margin-top: 80rpx;
  color: #fff;
  border: 2rpx solid #00c8b6;
  background-color: #00c8b6;
  font-size: 32rpx;
  font-weight: bold;
}

小程序后台-Django dwr

setting信息

"""
Django settings for auction project.
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '^#&=_59i4qviix7_way6!vvm)p+3i_mtw50$sbgh$1sx+q6@@f'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api.apps.ApiConfig', #注入app
    'rest_framework',   #注入rest_framework
]

……

WSGI_APPLICATION = 'auction.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


……


STATIC_URL = '/static/'

TEMPLATE_DIRS = (os.path.join(BASE_DIR,  'templates'),)

#redis缓存
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
            # "PASSWORD": "密码",
        }
    }
}

TEMPLATE_ID = '5973x'
TENCENT_SIGN = '个人测试'
TENCENT_APP_ID = '1400364xx'
TENCENT_CITY = 'ap-guangzhou'
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")

# log服务
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False, # 禁用已经存在的logger实例
    # 日志文件的格式
    'formatters': {
        # 详细的日志格式
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        # 简单的日志格式
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },

    },

    'handlers': {
    # 在终端打印
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
            'formatter': 'simple'
        },

        # 默认的
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "auction_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 3,  # 最多备份几个
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        # 专门用来记错误日志
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "auction_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
    },
    'loggers': {
        # 默认的logger应用如下配置
        '': {
            'handlers': ['default', 'console', 'error'],  # 上线之后可以把'console'移除
            'level': 'DEBUG',
            'propagate': True,  # 向不向更高级别的logger传递
        },
    }
}

主urls.py

from django.contrib import admin
from django.urls import path,include,re_path,register_converter

urlpatterns = [
    path('admin/', admin.site.urls),
    # 路由配置
    # re_path(r'^articles/2003/$', views.special_case_2003),
    # re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),  # 传递位置参数
    # # 有名分组(就是给分组起个名字,这样定义的好处就是按照关键字参数去传参了,指名道姓的方式)
    # re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

    re_path(r"api/", include(('api.urls', 'api'))),  # 分发
]

应用urls.py

from django.contrib import admin
from django.urls import path,include,re_path
from api import views
urlpatterns = [
    path('admin/', admin.site.urls),
    # 路由配置
    # re_path(r'^articles/2003/$', views.special_case_2003),
    # re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),  # 传递位置参数
    # # 有名分组(就是给分组起个名字,这样定义的好处就是按照关键字参数去传参了,指名道姓的方式)
    # re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

    re_path(r"login/", views.LoginView.as_view()),  # 分发
    re_path(r"message/", views.MessageView.as_view()),  # 分发
]
views.py
import uuid
import random
from rest_framework.views import APIView
from rest_framework.response import Response
from django_redis import get_redis_connection
from api import models
from api.utils.tencent.msg import send_message
from api.serializer.account import MessageSerializer, LoginSerializer
from common.Auc_Log import auc_logger

# Create your views here.


class LoginView(APIView):
    def post(self, request, *args, **kwargs):
        """
        1. 校验手机号是否合法
        2. 校验验证码,redis
            - 无验证码
            - 有验证码,输入错误
            - 有验证码,成功

        4. 将一些信息返回给小程序
        """
        ser = LoginSerializer(data=request.data)
        if not ser.is_valid():
            return Response({"status": False, 'message': '验证码错误'})

        # 3. 去数据库中获取用户信息(获取/创建)
        phone = ser.validated_data.get('phone')
        # user = models.UserInfo.objects.filter(phone=phone).first()
        # auc_logger.info(user.token)
        user_object, flag = models.UserInfo.objects.get_or_create(phone=phone)
        print(user_object,flag)
        user_object.token = str(uuid.uuid4())
        user_object.save()

        return Response({"status": True, "data": {"token": user_object.token, 'phone': phone}})


# Create your views here.


class MessageView(APIView):
    def get(self, request, *args, **kwargs):
        """
        发送手机短信验证码
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        # 1.获取手机号
        # 2.手机格式校验
        ser = MessageSerializer(data=request.query_params)
        if not ser.is_valid():
            return Response({'status': False, 'message': '手机格式错误'})
        phone = ser.validated_data.get('phone')

        # 3.生成随机验证码
        random_code = random.randint(1000, 9999)
        # 5.把验证码+手机号保留(60s过期)

        auc_logger.info('random_code={}'.format(random_code))
        # result = send_message(phone, random_code)
        # if not result:
        #     return Response({"status": False, 'message': '短信发送失败'})

        """
        #   5.1 搭建redis服务器(云redis)
        #   5.2 django中方便使用redis的模块 django-redis
               配置:
                    CACHES = {
                        "default": {
                            "BACKEND": "django_redis.cache.RedisCache",
                            "LOCATION": "redis://127.0.0.1:6379",
                            "OPTIONS": {
                                "CLIENT_CLASS": "django_redis.client.DefaultClient",
                                "CONNECTION_POOL_KWARGS": {"max_connections": 100}
                                # "PASSWORD": "密码",
                            }
                        }
                    }
                使用:
        """

        conn = get_redis_connection()
        conn.set(phone, random_code, ex=60)

        return Response({"status": True, 'message': '发送成功'})

用户校验:account.py

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from django_redis import get_redis_connection

from .validators import phone_validator

class MessageSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号',validators=[phone_validator,])


class LoginSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
    code = serializers.CharField(label='短信验证码')
    # 钩子函数
    def validate_code(self, value):
        if len(value) !=4:
            raise ValidationError('短信格式错误')
        if not value.isdecimal():
            raise ValidationError('短信格式错误')

        phone = self.initial_data.get('phone')
        conn = get_redis_connection()
        code = conn.get(phone)
        if not code:
            raise ValidationError('验证码过期')
        if value != code.decode('utf-8'):
            raise ValidationError('验证码错误')

        return value
发短信: msg.py
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.sms.v20190711 import sms_client, models


from django.conf import settings
import os
from common.Auc_Log import auc_logger

def send_message(phone,random_code):
    """
    发送短信验证码
        验证码发送到手机上,购买服务器进行发送短信:腾讯云
        1.注册腾讯云,开通腾讯云短信。
        2.创建应用
            SDK AppID = 1400302209
        3.申请签名(个人:公众号)
            ID      名称
            260514     Python之路
        4.申请模板
            ID      名称
            516680    miniprogram
        5.申请腾讯云API https://console.cloud.tencent.com/cam/capi
            SecretId:
            SecretKey:
        6.调用相关接口去发送短信 https://cloud.tencent.com/document/product/382/38778
            SDK,写好的工具。

    """
    try:
        phone = "{}{}".format("+86", phone)
        cred = credential.Credential(os.environ.get("secretId"),
                os.environ.get("secretKey"))
        client = sms_client.SmsClient(cred, settings.TENCENT_CITY)

        req = models.SendSmsRequest()
        req.SmsSdkAppid = settings.TENCENT_APP_ID
        # 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看
        req.Sign = settings.TENCENT_SIGN
        # 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
        req.PhoneNumberSet = [phone, ]
        # 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看
        req.TemplateID = settings.TEMPLATE_ID
        # 模板参数: 若无模板参数,则设置为空
        req.TemplateParamSet = [random_code, ]

        resp = client.SendSms(req)
        auc_logger.info(resp)
        # 输出json格式的字符串回包
        if resp.SendStatusSet[0].Code == "Ok":
            return True

    except TencentCloudSDKException as err:
        pass

model.py

from django.db import models

# Create your models here.
class UserInfo(models.Model):
    phone = models.CharField(verbose_name='手机号',max_length=11,unique=True)

    token = models.CharField(verbose_name='用户token',max_length=64, null=True,blank=True)

手机验证:validators.py

import re
from rest_framework.exceptions import ValidationError


def phone_validator(value):
    if not re.match(r"^(1[3|4|5|6|7|8|9])\d{9}$",value):
        raise ValidationError('手机格式错误')

log服务Auc_Log.py

import logging
# 生成一个以当前文件名为名字的logger实例
logger = logging.getLogger(__name__)
# 生成一个名为auction的logger实例
auc_logger = logging.getLogger("auction")
CDSY,CDSY.XYZ
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门