一、简介
小程序注册登录功能
python后台API开发
阿里云验证码发送
二、小程序注册页面
<!--pages/register/register.wxml--> <view wx:if="{{!success}}"> <view class=‘row‘> <view class=‘info‘> <input class=‘info-input1‘ bindinput="handleInputPhone" placeholder="请输入你的手机号" /> </view> <button class=‘button_yam‘ bindtap=‘doGetCode‘ disabled=‘{{disabled}}‘ style="background-color:{{color}}">{{text}}</button> </view> <view class=‘row‘> <view class=‘info‘> <input class=‘info-input‘ bindinput="handleVerificationCode" placeholder="请输入你的验证码" /> </view> </view> <view class=‘row‘> <view class=‘info‘> <input type=‘password‘ class=‘info-input‘ bindinput="handleNewChanges" placeholder="请输入你的密码" /> </view> </view> <view class=‘row‘> <view class=‘info‘> <input type=‘password‘ class=‘info-input‘ bindinput="handleNewChangesAgain" placeholder="请重新输入你的密码" /> </view> </view> <button class=‘submit‘ bindtap=‘submit‘>提交</button> </view> <view class=‘success‘ wx:if="{{success}}"> <view class=‘cheer‘> <icon type="success" size="24" /> 恭喜您注册成功!</view> <button type="default" class=‘return‘ bindtap=‘return_home‘>返回首页</button> </view>
/* pages/register/register.wxss */ page{ background: #F0F0F0 ; } .row{ margin-top: 20rpx; overflow: hidden; line-height: 100rpx; border-bottom: 1rpx solid #ccc; margin-left: 20rpx; margin-right: 20rpx; color: #777; background: #fff; } .info-input{ height: 100rpx; margin-left: 50rpx; color: #777; float: left; } .info-input1{ height: 100rpx; margin-left: 50rpx; color: #777; float: left; width: 420rpx; } .button_yam{ width: 200rpx !important; height: 70rpx; line-height: 70rpx; font-size: 28rpx; background: #33FF99; float: left; margin-left: 10rpx; margin-top: 15rpx; color: #FFFFFF; padding: 0; } .submit{ margin-top: 50rpx; margin-left: 20rpx; margin-right: 20rpx; background: #00CCFF; color: #FFFFFF; } .success{ background: #ffffff; } .cheer{ text-align: center; line-height: 400rpx; font-size: 60rpx; position: relative; } .return{ margin: 20rpx; }
Page({ /** * 页面的初始数据 */ data: { text: ‘获取验证码‘, //按钮文字 currentTime: 61, //倒计时 disabled: false, //按钮是否禁用 phone: ‘‘, //获取到的手机栏中的值 VerificationCode: ‘‘, Code: ‘‘, NewChanges: ‘‘, NewChangesAgain: ‘‘, success: false, state: ‘‘ }, /** * 获取验证码 */ return_home: function (e) { wx.navigateTo({ url: ‘/pages/login/login‘, }) }, handleInputPhone: function (e) { this.setData({ phone: e.detail.value }) }, handleVerificationCode: function (e) { console.log(e); this.setData({ Code: e.detail.value }) }, handleNewChanges: function (e) { console.log(e); this.setData({ NewChanges: e.detail.value }) }, handleNewChangesAgain: function (e) { console.log(e); this.setData({ NewChangesAgain: e.detail.value }) }, doGetCode: function () { var that = this; that.setData({ disabled: true, //只要点击了按钮就让按钮禁用 (避免正常情况下多次触发定时器事件) color: ‘#ccc‘, }) var phone = that.data.phone; var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值 var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空 var phone = that.data.phone; var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值 var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空 wx.request({ url: ‘‘, //后端判断是否已被注册, 已被注册返回1 ,未被注册返回0 method: "GET", header: { ‘content-type‘: ‘application/x-www-form-urlencoded‘ }, success: function (res) { that.setData({ state: res.data }) if (phone == ‘‘) { warn = "号码不能为空"; } else if (phone.trim().length != 11 || !/^1[3|4|5|6|7|8|9]\d{9}$/.test(phone)) { warn = "手机号格式不正确"; } //手机号已被注册提示信息 else if (that.data.state == 1) { //判断是否被注册 warn = "手机号已被注册"; } else { wx.request({ url: ‘‘, //填写发送验证码接口 method: "POST", data: { coachid: that.data.phone }, header: { ‘content-type‘: ‘application/x-www-form-urlencoded‘ }, success: function (res) { console.log(res.data) that.setData({ VerificationCode: res.data.verifycode }) //当手机号正确的时候提示用户短信验证码已经发送 wx.showToast({ title: ‘短信验证码已发送‘, icon: ‘none‘, duration: 2000 }); //设置一分钟的倒计时 var interval = setInterval(function () { currentTime--; //每执行一次让倒计时秒数减一 that.setData({ text: currentTime + ‘s‘, //按钮文字变成倒计时对应秒数 }) //如果当秒数小于等于0时 停止计时器 且按钮文字变成重新发送 且按钮变成可用状态 倒计时的秒数也要恢复成默认秒数 即让获取验证码的按钮恢复到初始化状态只改变按钮文字 if (currentTime <= 0) { clearInterval(interval) that.setData({ text: ‘重新发送‘, currentTime: 61, disabled: false, color: ‘#33FF99‘ }) } }, 100); } }) }; //判断 当提示错误信息文字不为空 即手机号输入有问题时提示用户错误信息 并且提示完之后一定要让按钮为可用状态 因为点击按钮时设置了只要点击了按钮就让按钮禁用的情况 if (warn != null) { wx.showModal({ title: ‘提示‘, content: warn }) that.setData({ disabled: false, color: ‘#33FF99‘ }) return; } } }) }, submit: function (e) { var that = this if (this.data.Code == ‘‘) { wx.showToast({ title: ‘请输入验证码‘, image: ‘/images/error.png‘, duration: 2000 }) return } else if (this.data.Code != this.data.VerificationCode) { wx.showToast({ title: ‘验证码错误‘, image: ‘/images/error.png‘, duration: 2000 }) return } else if (this.data.NewChanges == ‘‘) { wx.showToast({ title: ‘请输入密码‘, image: ‘/images/error.png‘, duration: 2000 }) return } else if (this.data.NewChangesAgain != this.data.NewChanges) { wx.showToast({ title: ‘两次密码不一致‘, image: ‘/images/error.png‘, duration: 2000 }) return } else { var that = this var phone = that.data.phone; wx.request({ url: getApp().globalData.baseUrl + ‘/Coachs/insert‘ , method: "POST", data: { coachid: phone, coachpassword: that.data.NewChanges }, header: { "content-type": "application/x-www-form-urlencoded" }, success: function (res) { wx.showToast({ title: ‘提交成功~‘, icon: ‘loading‘, duration: 2000 }) console.log(res) that.setData({ success: true }) } }) } }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
三、Python后台API
1 先搭一个环境
2 配置环境
2.1 安装django 1.11.7
pip install django==1.11.7
2.2 安装drf
pip install dajngorestframework
2.3 setting文件中注册rest_framework
INSTALLED_APPS 中添加 ‘rest_framework‘
2.4 修改WebchatAPI下面的url
from django.contrib import admin # from django.urls import path from django.conf.urls import url,include urlpatterns = [ url(r‘^api/‘,include(‘api.urls‘,namespace=‘api‘) ), ]
2.5 修改API下面的url
from django.conf.urls import url, include from django.contrib import admin from API import views urlpatterns = [ url(r‘^isExist/‘, views.IsExist.as_view()), # url(r‘^login/‘, views.LoginView.as_view()), ]
2.6 修改view中逻辑
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView from rest_framework.response import Response # Create your views here. class IsExist(APIView): def post(self,request,*args,**kwargs): print(request.data) return Response({"state":1})
2.7 微信端 获取验证码逻辑
doGetCode: function () { var that = this; that.setData({ disabled: true, //只要点击了按钮就让按钮禁用 (避免正常情况下多次触发定时器事件) color: ‘#ccc‘, }) var phone = that.data.phone; var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值 var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空 var phone = that.data.phone; var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值 var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空 wx.request({ url: ‘http://127.0.0.1:8000/API/isExist/‘, //后端判断是否已被注册, 已被注册返回1 ,未被注册返回0 method: "POST", data:{phone:this.data.phone}, header: { ‘content-type‘: ‘application/x-www-form-urlencoded‘ }, success: function (res) { that.setData({ state: res.data.state }) if (phone == ‘‘) { warn = "号码不能为空"; } else if (phone.trim().length != 11 || !/^1[3|4|5|6|7|8|9]\d{9}$/.test(phone)) { warn = "手机号格式不正确"; } //手机号已被注册提示信息 else if (that.data.state == 1) { //判断是否被注册 warn = "手机号已被注册"; } else { wx.request({ url: ‘‘, //填写发送验证码接口 method: "POST", data: { coachid: that.data.phone }, header: { ‘content-type‘: ‘application/x-www-form-urlencoded‘ }, success: function (res) { console.log(res.data) that.setData({ VerificationCode: res.data.verifycode }) //当手机号正确的时候提示用户短信验证码已经发送 wx.showToast({ title: ‘短信验证码已发送‘, icon: ‘none‘, duration: 2000 }); //设置一分钟的倒计时 var interval = setInterval(function () { currentTime--; //每执行一次让倒计时秒数减一 that.setData({ text: currentTime + ‘s‘, //按钮文字变成倒计时对应秒数 }) //如果当秒数小于等于0时 停止计时器 且按钮文字变成重新发送 且按钮变成可用状态 倒计时的秒数也要恢复成默认秒数 即让获取验证码的按钮恢复到初始化状态只改变按钮文字 if (currentTime <= 0) { clearInterval(interval) that.setData({ text: ‘重新发送‘, currentTime: 61, disabled: false, color: ‘#33FF99‘ }) } }, 100); } }) }; //判断 当提示错误信息文字不为空 即手机号输入有问题时提示用户错误信息 并且提示完之后一定要让按钮为可用状态 因为点击按钮时设置了只要点击了按钮就让按钮禁用的情况 if (warn != null) { wx.showModal({ title: ‘提示‘, content: warn }) that.setData({ disabled: false, color: ‘#33FF99‘ }) return; } } }) },
3 期间遇到的问题
问题1:如果运行过程中 django1.11 启动错误:Generator expression must be parenthesized
原因:由于django 1.11版本和python3.7版本不兼容, 2.0版本以后的Django修复了这个问题
解决方案:
方法1.找到对应路径下的widgets.py,将逗号删除即可
问题2:微信小程序运行过程中报错
原因:微信的安全验证
1:微信的网络请求api必须是https
2:在微信小程序后台设置
解决方案:
勾选 不进行域名校验
4 测试
四、对接阿里云
1:首先你得有一个云服务器,如果没有可以找我
2:我们再云服务器上开通短信服务,设置验证码模板
3:安装Redis数据库,用于存储验证码,并判断是否过期。我们这里用宝塔安装
4:发送短信代码
4.1 首先添加一个文件夹就叫Ali,封装一个发送短信的统一接口
需要安装python的SDK:pip install aliyun-python-sdk-core-v3
4.2 为了方便管理我们把一些重要的常量,放置在一个yaml文件中
pip install pyyaml
# 项目的域名IP #PROJECT_ADDR: http://39.99.213.203:8000 # 媒体文件默认地址 #MEDIA_ADDR: http://39.99.213.203:8000/media/ # mysql连接信息 #Mysql: # NAME: ZebraHome # USER: szBellMen # PASSWORD: Zebra123456 # HOST: 39.99.213.203 # PORT: 3306 # redis连接信息 Redis: NAME: 0 HOST: 39.99.213.203 PORT: 6379 Redis_Celery_BROKER: NAME: 1 HOST: 39.99.213.203 PORT: 6379 Redis_Celery_RESULT: NAME: 2 HOST: 39.99.213.203 PORT: 6379 # 连接物联网平台的账户 Aliyun: accessKeyId: LTAI4G4naVRo7ry8LYmu2Enm accessKeySecret: bQkXA5ENZUyGhuQc3mO4BaM7ys3CR6 regionId: cn-hangzhou #消息推送相关 #AppSetting: # adminAccount: d4281673-3bc0-45e4-88dc-157c03307c97 # iosAppKey: 29058022 # androidAppKey: 29161307 # regionId: cn-hangzhou #短信服务相关 SMSSetting: adminPhone: 18896527725 signName: 斑马之家 templateCode: SMS_186614747 regionId: cn-hangzhou messageType: SmsUp queueName: Alicom-Queue-1745978963455427-SmsUp endpoint: http://1943695596114318.mns.cn-hangzhou.aliyuncs.com ##语音服务相关 #SCSetting: # #主叫号码,必须是已购买的号码。 # CALLEDSHOWNUMBER: 051068514916 ##系统中默认天数 #Days: # #注意一旦设置后,告警求助、设备共享、设备消息、用户信息都是默认7天 # NOTIFY_INFO_DAYS: 7
4.3 同时需要在setting文件设置
# 导入配置文件,获取当前的目录 import os, yaml yamlPath = os.path.join(BASE_DIR, "conf", "configuration.yaml") # print(u"yamlPath=%s" % yamlPath) with open(yamlPath, ‘rb‘) as f: yamlResult = yaml.load(f, Loader=yaml.FullLoader)
4.4 创建APIModel主要是基于面向对象思想,用于设置消息模型Model
‘‘‘ # 说明:数据模型,用于方法之间数据传递 # 优点:调用时,方便快捷(只需要给相关属性赋值) # 如果发生变动,可快速统一修改,ps:统一规范消息的生成规则 # 给变量赋值时候做校验,提前规避风险。ps:对字段做非空校验,对字段类型做校验 ‘‘‘ from WechatAPI.settings import yamlResult from datetime import datetime from API.APIEnum import SMSTemplateCode import json import random import logging import os # @Time : 2020-3-27 14:13:27 # @Author : Yango # @Title : 短信服务发送模型 # @Content : 用于短信,各个业务场景调用阿里云接口时所使用的对象模型 # @Software: PyCharm class SMSModel: ADMINPHONE = yamlResult["SMSSetting"][‘adminPhone‘] SIGNNAME = yamlResult["SMSSetting"][‘signName‘] TEMPLATECODE = yamlResult["SMSSetting"][‘templateCode‘] ACCESSKEYID = yamlResult["Aliyun"][‘accessKeyId‘] # 接收短信的手机号码。 # 必填,string 类型 # 支持对单个、多个手机号码发送短信,手机号码之间以英文逗号(,)分隔。 @property def phone_numbers(self): if hasattr(self, ‘_SMSModel__phone_numbers‘): return self.__phone_numbers else: return self.get_phone_numbers_by_user_list @phone_numbers.setter def phone_numbers(self, value): if value: self.__phone_numbers = value else: print("error:【SMSModel】中(phone_numbers)不能为空!") # 阿里云短信签名名称。 # 必填,string 类型 # 可在控制台签名管理页面签名名称一列查看,必须是已添加、并通过审核的短信签名。 @property def sign_name(self): return self.__sign_name if hasattr(self, ‘_SMSModel__sign_name‘) else self.SIGNNAME @sign_name.setter def sign_name(self, value): if value: self.__sign_name = value else: print("error:【SMSModel】中(sign_name)不能为空!") # 短信模板ID。 # 必填,string 类型 # 可在控制台签名管理页面模板CODE一列查看,必须是已添加、并通过审核的模板。 @property def template_code(self): return self.__template_code if hasattr(self, ‘_SMSModel__template_code‘) else self.TEMPLATECODE @template_code.setter def template_code(self, value): if value: self.__template_code = value else: print("error:【SMSModel】中(template_code)不能为空!") # 主账号AccessKey的ID。 # 非必填,string 类型 @property def access_key_id(self): return self.__access_key_id if hasattr(self, ‘_SMSModel__access_key_id‘) else self.ACCESSKEYID @access_key_id.setter def access_key_id(self, value): self.__access_key_id = value # 系统规定参数。 # 非必填,string 类型,取值:SendSms。 @property def action(self): return self.__action if hasattr(self, ‘_SMSModel__action‘) else "SendSms" @action.setter def action(self, value): self.__action = value # 外部流水扩展字段。 # 非必填,string 类型 @property def out_id(self): return self.__out_id if hasattr(self, ‘_SMSModel__out_id‘) else "" @out_id.setter def out_id(self, value): self.__out_id = value # 上行短信扩展码,无特殊需要此字段的用户请忽略此字段。 # 非必填,string 类型 @property def sms_up_extend_dode(self): return self.__sms_up_extend_dode if hasattr(self, ‘_SMSModel__sms_up_extend_dode‘) else "" @sms_up_extend_dode.setter def sms_up_extend_dode(self, value): self.__sms_up_extend_dode = value # 短信模板变量对应的实际值,JSON格式。 # 非必填,json 类型 # {"code": "1111"} @property def template_param(self): if hasattr(self, ‘_SMSModel__template_param‘): return self.__template_param elif hasattr(self, ‘_SMSModel__template_code‘): # 信息变更验证码 # 修改密码验证码 # 用户注册验证码 # 登录异常验证码 # 登录确认验证码 # 身份验证验证码 if self.__template_code == SMSTemplateCode.CHANGEINFO.name or self.__template_code == SMSTemplateCode.CHANGEINFOCONFIRM.name or self.__template_code == SMSTemplateCode.CHANGEPASSWORD.name or self.__template_code == SMSTemplateCode.USERREGISTER.name or self.__template_code == SMSTemplateCode.LOGINERROR.name or self.__template_code == SMSTemplateCode.LOGINCONFIRM.name or self.__template_code == SMSTemplateCode.Authentication.name: return self.get_verification_code elif self.__template_code == SMSTemplateCode.HELP.name or self.__template_code == SMSTemplateCode.ALARM.name: return self.get_help_alarm_content else: return ‘{"code": "7725"}‘ @template_param.setter def template_param(self, value): self.__template_param = value #######业务特有######## @property def user_list(self): return self.__user_list if hasattr(self, ‘_SMSModel__user_list‘) else [] @user_list.setter def user_list(self, value): if isinstance(value, list): self.__user_list = value else: print("error:【SMSModel】中(user_list)输入类型与预设类型不一致") @property def sms_code(self): return self.__sms_code if hasattr(self, ‘_SMSModel__sms_code‘) else "" @sms_code.setter def sms_code(self, value): self.__sms_code = value @property def code_length(self): return self.__code_length if hasattr(self, ‘_SMSModel__code_length‘) else 4 @code_length.setter def code_length(self, value): if isinstance(value, int): self.__code_length = value else: print("error:【SMSModel】中(code_length)输入类型与预设类型不一致") @property def code_type(self): return self.__code_type if hasattr(self, ‘_SMSModel__code_type‘) else "only_num" @code_type.setter def code_type(self, value): self.__code_type = value ########业务特有######## # 将列表形式的user_list值,通过‘,‘拼接成字符串;如果user_list没有传递值,则默认返回ADMINPHONE(管理员联系方式) # return :string ps:字符串 # retrun :用户1电话,用户2电话.‘ ps:多个值使用逗号分隔. # 注意:电话号码一次最多1000个的限制,调用此方法时候,需要把【user_list】赋值 @property def get_phone_numbers_by_user_list(self): return ‘,‘.join(self.__user_list) if hasattr(self, ‘_SMSModel__user_list‘) else self.ADMINPHONE # 获取验证码 # return :string ps:字符串 # retrun :验证码 # 注意:调用此方法时候,需要把【template_code】赋值 @property def get_verification_code(self): template_code = self.__template_code if hasattr(self, ‘_SMSModel__template_code‘) else self.TEMPLATECODE if SMSTemplateCode[template_code]: tecd = SMSTemplateCode[template_code] if tecd and tecd.name == "CHANGEINFO" or tecd.name == "CHANGEINFOCONFIRM" or tecd.name == "CHANGEPASSWORD" or tecd.name == "USERREGISTER" or tecd.name == "LOGINERROR" or tecd.name == "LOGINCONFIRM" or tecd.name == "Authentication": str_code = self.make_code temp_param = {} temp_param.setdefault("code", str_code) template_parameters = json.dumps(temp_param) self.__template_param = template_parameters self.template_code = tecd.value return str_code else: return "7725" # 生成随机码,内部调用, # return :string ps:字符串 # retrun :验证码 # 注意:调用此方法时候,需要把【template_code】赋值 @property def make_code(self): code_list = [] if self.code_type == "only_num": for i in range(self.code_length): code_list.append(str(random.randint(0, 9))) else: for i in range(self.code_length): random_num = random.randint(0, 9) # 随机生成0-9的数字 # 利用random.randint()函数生成一个随机整数a,使得65<=a<=90 # 对应从“A”到“Z”的ASCII码 a = random.randint(65, 90) b = random.randint(97, 122) random_uppercase_letter = chr(a) random_lowercase_letter = chr(b) code_list.append(str(random_num)) code_list.append(random_uppercase_letter) code_list.append(random_lowercase_letter) verification_code = ‘‘.join(code_list) return verification_code
4.5 同时相应的配套设备枚举类型也得跟上
from enum import Enum from enum import unique @unique class SMSTemplateCode(Enum): ######模板类型验证码###### # 信息变更验证码 CHANGEINFO = "1" # 信息变更确认验证码 CHANGEINFOCONFIRM = "2" # 修改密码验证码 CHANGEPASSWORD = "3" # 用户注册验证码 USERREGISTER = "SMS_186614747" # 登录异常验证码 LOGINERROR = "4" # 登录确认验证码 LOGINCONFIRM = "5" # 身份验证验证码 Authentication = "6" ######模板类型验证码###### # 设备求助模板 HELP = "7" # 设备告警模板 ALARM = "8"
4.6 安装django-redis 用于存储验证码
pip install django-redis
4.7 设置settings.py
# ============配置reids================== # 配置Redis为Django缓存 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", # 地址 "LOCATION": "redis://{}:{}/{}".format(yamlResult[‘Redis‘][‘HOST‘], yamlResult[‘Redis‘][‘PORT‘], yamlResult[‘Redis‘][‘NAME‘], ), "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} } } } # 将session缓存在Redis中 # SESSION_ENGINE = "django.contrib.sessions.backends.cache" # SESSION_CACHE_ALIAS = "default" # # ============配置reids==================
至此,短信发送功能已经可以实现
五、完善注册功能
注册时候前后台手机格式验证
注册时后台验证是否已经注册
验证验证码是否正确,是否过期
5.1 创建mysql数据库(WechatAPI),创建用户模型(UserInfo)
5.2.1 安装 pip install pymysql
5.2.2 在项目里的__init__.py文件中中加入以下代码:
import pymysql
pymysql.install_as_MySQLdb()
5.2.3 修改配置文件
INSTALLED_APPS = [ ‘django.contrib.admin‘, ‘django.contrib.auth‘, ‘django.contrib.contenttypes‘, ‘django.contrib.sessions‘, ‘django.contrib.messages‘, ‘django.contrib.staticfiles‘, ‘API‘, ‘rest_framework‘, ] # # ============配置mysql数据库--开始================== DATABASES = { ‘default‘: { ‘ENGINE‘: ‘django.db.backends.mysql‘, ‘NAME‘: yamlResult[‘Mysql‘][‘NAME‘], ‘USER‘: yamlResult[‘Mysql‘][‘USER‘], ‘PASSWORD‘: yamlResult[‘Mysql‘][‘PASSWORD‘], ‘HOST‘: yamlResult[‘Mysql‘][‘HOST‘], ‘PORT‘: yamlResult[‘Mysql‘][‘PORT‘], ‘OPTIONS‘: {‘init_command‘: ‘SET default_storage_engine=INNODB;‘, "init_command": "SET foreign_key_checks = 0;", ‘charset‘: ‘utf8mb4‘} } } # # ============配置mysql数据库--结束==================
5.2.4 UserInfo模型
from django.db import models from django.utils import timezone import uuid # Create your models here. # 用户表 class UserInfo(models.Model): id = models.CharField(verbose_name=u‘用户ID-用户唯一标识‘, max_length=50, primary_key=True, auto_created=True, default=uuid.uuid4, editable=False) # 主键 user_type = models.CharField(verbose_name=u‘用户类型‘, max_length=20, default=u‘1‘) # 新增类型,普通/会员 username = models.CharField(verbose_name=u‘用户名‘, max_length=100) id_card = models.CharField(verbose_name=u‘身份证‘, max_length=20) phone = models.CharField(verbose_name=u‘手机号‘, max_length=20) password = models.CharField(verbose_name=u‘密码‘, max_length=100) email = models.EmailField(verbose_name=u‘邮箱‘) QQId = models.CharField(verbose_name=u‘QQ号id‘, max_length=50, null=True) wechatId = models.CharField(verbose_name=u‘微信 账号id‘, max_length=50, null=True) weiboId = models.CharField(verbose_name=u‘微博 账号id‘, max_length=50, null=True) create_date = models.DateTimeField(verbose_name=u‘创建时间‘, default=timezone.now) # avatar = models.ImageField(upload_to=‘avatar‘, default=‘‘, verbose_name=‘头像‘) # 该条记录状态 status = models.CharField(verbose_name=u‘该条记录状态‘, max_length=10, default=u‘1‘) # 正常,删除 def __str__(self): return self.id class Meta: db_table = ‘UserInfo‘
5.2.5 生成数据表
python manage.py makemigrations
python manage.py migrate
至此,要保证数据库中生成相应的实体模型
5.2 业务层逻辑验证 与 手机前端验证
5.2.1 序列化器,完成手机格式的后台校验
5.2.2 校验用户是否已注册
5.2.3 发送验证码,并把数据写入到redis数据库
5.2.4 提交的时候进行验证码检查
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.exceptions import ValidationError from API.APIModels import SMSModel from Ali.SMSMessage import sendInfo from django_redis import get_redis_connection from API.APIEnum import SMSTemplateCode from API.models import UserInfo import re # Create your views here. #########定义一些校验规则--开始######### def phone_validator(values): if not re.match(r"^1[3|4|5|6|7|8|9]\d{9}$", values): raise ValidationError(‘手机格式错误‘) #########定义一些校验规则--结束######### # 定义一个序列化器 class RegisterMessageSerializer(serializers.Serializer): phone = serializers.CharField(label=‘手机号‘, validators=[phone_validator, ]) class IsExist(APIView): def post(self, request, *args, **kwargs): ser = RegisterMessageSerializer(data=request.data) if not ser.is_valid(): return Response({"status": "fail", "msg": "手机号码格式有误"}) else: phone = request.data.get("phone") # 判断用户是否注册 if UserInfo.objects.filter(phone=phone).first(): return Response({"status": "fail", "msg": "用户已注册!"}) else: return Response({"status": "success", "msg": "该用户尚未注册!"}) class SendCode(APIView): def post(self, request, *args, **kwargs): phone = request.data.get("phone") template_code = request.data.get("template_code") print(phone, template_code) if not all([phone, template_code]): return Response({"status": "fail", "verifycode": "", "msg": "必要入参不能为空!"}) sms = SMSModel() sms.phone_numbers = phone sms.template_code = template_code verifycode = sms.get_verification_code # 获取验证码 res = sendInfo(sms) # 发送验证码 if res: # 将验证码和手机号存入到redis,保活60s r = get_redis_connection() r.set(phone, verifycode, 600) # 该值的有效期为 return Response({"status": "success", "verifycode": verifycode, "msg": "验证码发送成功!"}) else: return Response({"status": "fail", "verifycode": "", "msg": "验证码发送失败!请稍后重试"}) class UserRegister(APIView): def post(self, request, *args, **kwargs): phone = request.data.get("phone") password = request.data.get("password") verificationCode = request.data.get("verificationCode") if not all([phone, password,verificationCode]): return Response({"status": "fail", "verifycode": "", "msg": "必要入参不能为空!"}) r = get_redis_connection() true_verifycode = r.get(phone) if not true_verifycode: return Response({"status": "fail", "verifycode": "", "msg": "验证码已过期,请重新发送!"}) true_verifycode = true_verifycode.decode() # 将字节转为字符串型 if verificationCode == true_verifycode: UserInfo.objects.create(phone=phone,password=password) return Response({"status": "success", "verifycode": true_verifycode, "msg": "验证码通过!"}) else: return Response({"status": "fail", "verifycode": true_verifycode, "msg": "验证码不正确,请重新发送!"})
5.2.5 手机端js
Page({ /** * 页面的初始数据 */ data: { text: ‘获取验证码‘, //按钮文字 currentTime: 61, //倒计时 disabled: false, //按钮是否禁用 phone: ‘‘, //获取到的手机栏中的值 VerificationCode: ‘‘, Code: ‘‘, NewChanges: ‘‘, NewChangesAgain: ‘‘, success: false, state: ‘‘, result: {} }, /** * 获取验证码 */ return_home: function (e) { wx.navigateTo({ url: ‘/pages/login/login‘, }) }, handleInputPhone: function (e) { this.setData({ phone: e.detail.value }) }, handleVerificationCode: function (e) { console.log(e); this.setData({ Code: e.detail.value }) }, handleNewChanges: function (e) { console.log(e); this.setData({ NewChanges: e.detail.value }) }, handleNewChangesAgain: function (e) { console.log(e); this.setData({ NewChangesAgain: e.detail.value }) }, doGetCode: function () { var that = this; that.setData({ disabled: true, //只要点击了按钮就让按钮禁用 (避免正常情况下多次触发定时器事件) color: ‘#ccc‘, }) var phone = that.data.phone; var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值 var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空 var phone = that.data.phone; var currentTime = that.data.currentTime //把手机号跟倒计时值变例成js值 var warn = null; //warn为当手机号为空或格式不正确时提示用户的文字,默认为空 if (phone == ‘‘) { warn = "号码不能为空"; } else if (phone.trim().length != 11 || !/^1[3|4|5|6|7|8|9]\d{9}$/.test(phone)) { warn = "手机号格式不正确"; } if (warn != null) { wx.showModal({ title: ‘提示‘, content: warn }) that.setData({ disabled: false, color: ‘#33FF99‘ }) return; } wx.request({ url: getApp().globalData.baseUrl +‘/API/isExist/‘, //后端判断是否已被注册, 已被注册返回1 ,未被注册返回0 method: "POST", data: { phone: this.data.phone }, header: { ‘content-type‘: ‘application/x-www-form-urlencoded‘ }, success: function (res) { that.setData({ result: res.data }) if (that.data.result.status == "fail") { //手机号已被注册提示信息 //判断是否被注册 warn = that.data.result.msg; } else { wx.request({ url: getApp().globalData.baseUrl +‘/API/sendCode/‘, //填写发送验证码接口 method: "POST", data: { phone: that.data.phone, template_code: "USERREGISTER" }, header: { ‘content-type‘: ‘application/x-www-form-urlencoded‘ }, success: function (res) { console.log(res.data) that.setData({ VerificationCode: res.data.verifycode, }) if (that.data.result.status == "fail") { //当手机号正确的时候提示用户短信验证码已经发送 wx.showToast({ title: that.data.result.msg, icon: ‘none‘, duration: 2000 }); } //设置一分钟的倒计时 var interval = setInterval(function () { currentTime--; //每执行一次让倒计时秒数减一 that.setData({ text: currentTime + ‘s‘, //按钮文字变成倒计时对应秒数 }) //如果当秒数小于等于0时 停止计时器 且按钮文字变成重新发送 且按钮变成可用状态 倒计时的秒数也要恢复成默认秒数 即让获取验证码的按钮恢复到初始化状态只改变按钮文字 if (currentTime <= 0) { clearInterval(interval) that.setData({ text: ‘重新发送‘, currentTime: 61, disabled: false, color: ‘#33FF99‘ }) } }, 1000); } }) }; //判断 当提示错误信息文字不为空 即手机号输入有问题时提示用户错误信息 并且提示完之后一定要让按钮为可用状态 因为点击按钮时设置了只要点击了按钮就让按钮禁用的情况 if (warn != null) { wx.showModal({ title: ‘提示‘, content: warn }) that.setData({ disabled: false, color: ‘#33FF99‘ }) return; } } }) }, submit: function (e) { var that = this if (this.data.Code == ‘‘) { wx.showToast({ title: ‘请输入验证码‘, image: ‘/static/images/error.png‘, duration: 2000 }) return } else if (this.data.Code != this.data.VerificationCode) { wx.showToast({ title: ‘验证码错误‘, image: ‘/static/images/error.png‘, duration: 2000 }) return } else if (this.data.NewChanges == ‘‘) { wx.showToast({ title: ‘请输入密码‘, image: ‘/static/images/error.png‘, duration: 2000 }) return } else if (this.data.NewChangesAgain != this.data.NewChanges) { wx.showToast({ title: ‘两次密码不一致‘, image: ‘/static/images/error.png‘, duration: 2000 }) return } else { var that = this var phone = that.data.phone; wx.request({ url: getApp().globalData.baseUrl + ‘/API/userRegister/‘, method: "POST", data: { phone: phone, password: that.data.NewChanges, verificationCode: this.data.VerificationCode }, header: { "content-type": "application/x-www-form-urlencoded" }, success: function (res) { wx.showToast({ title: ‘提交成功~‘, icon: ‘loading‘, duration: 2000 }) that.setData({ success: true }) } }) } }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
5.2.6 手机端css
/* pages/register/register.wxss */ page{ background: #F0F0F0 ; } .row{ margin-top: 20rpx; overflow: hidden; line-height: 100rpx; border-bottom: 1rpx solid #ccc; margin-left: 20rpx; margin-right: 20rpx; color: #777; background: #fff; } .info-input{ height: 100rpx; margin-left: 50rpx; color: #777; float: left; } .info-input1{ height: 100rpx; margin-left: 50rpx; color: #777; float: left; width: 420rpx; } .button_yam{ width: 200rpx !important; height: 70rpx; line-height: 70rpx; font-size: 28rpx; background: #33FF99; float: left; margin-left: 10rpx; margin-top: 15rpx; color: #FFFFFF; padding: 0; } .submit{ margin-top: 50rpx; margin-left: 20rpx; margin-right: 20rpx; background: #00CCFF; color: #FFFFFF; } .success{ background: #ffffff; } .cheer{ text-align: center; line-height: 400rpx; font-size: 60rpx; position: relative; } .return{ margin: 20rpx; }
5.2.7 手机端hml
<!--pages/register/register.wxml--> <view wx:if="{{!success}}"> <view class=‘row‘> <view class=‘info‘> <input class=‘info-input1‘ bindinput="handleInputPhone" placeholder="请输入你的手机号" /> </view> <button class=‘button_yam‘ bindtap=‘doGetCode‘ disabled=‘{{disabled}}‘ style="background-color:{{color}}">{{text}}</button> </view> <view class=‘row‘> <view class=‘info‘> <input class=‘info-input‘ bindinput="handleVerificationCode" placeholder="请输入你的验证码" /> </view> </view> <view class=‘row‘> <view class=‘info‘> <input type=‘password‘ class=‘info-input‘ bindinput="handleNewChanges" placeholder="请输入你的密码" /> </view> </view> <view class=‘row‘> <view class=‘info‘> <input type=‘password‘ class=‘info-input‘ bindinput="handleNewChangesAgain" placeholder="请重新输入你的密码" /> </view> </view> <button class=‘submit‘ bindtap=‘submit‘>提交</button> </view> <view class=‘success‘ wx:if="{{success}}"> <view class=‘cheer‘> <icon type="success" size="24" /> 恭喜您注册成功!</view> <button type="default" class=‘return‘ bindtap=‘return_home‘>返回首页</button> </view>
5.2.8 app设置全局变量
globalData: { userInfo: null, baseUrl:"http://127.0.0.1:8000" }
5.3 测试
5.3.1 测试不符合规范的手机号
5.3.2 测试验证码发送和redis数据库
5.3.3 提交的时候做验证校验
5.3.4 测试已注册用户重复注册