day04 功能开发
内容回顾
-
组件
-
view
-
text
-
image
-
button
-
navigtor
-
textarea
-
input
-
-
api
-
用户信息
-
地理位置
-
选择图片
-
跳转(非tabbar)
-
打开授权配置
-
发送请求(注意:https/后台设置)
-
提示框
-
-
数据绑定(双向绑定)
-
后端需要setData
-
前端用。。
-
-
腾讯云发送短信服务
-
后台配置频率
-
调用API进行发送:v3版本
-
今日概要
-
用户登录/注册
-
发布心情
-
图片上传(腾讯对象存储)
-
-
查看心情
-
查看心情详细
今日详细
1. 用户登录
1.1 发送短信
1.2 登录
知识点:
-
小程序公共对象(所有页面都可以调用此数据)
-
app.js:
小程序全局globalData(放公共数据 所有页面都可以调用)
App({ /** * 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) */ onLaunch: function () { }, globalData:{ userInfo: null, } })
其他页面操作公共值app.js(app.globalData)
var app = getApp(); Page({ data: { }, onShow:function(){ app.globalData } });
注意:修改globalData之后,其他页面以用的值不会自动变化,而是需要手动设置。
-
-
本地存储操作Storage
wx.getStorageSync('userInfo'); wx.setStorageSync('userInfo',"sdfsfd"); wx.removeStorageSync("userInfo")
-
页面调用栈
var pages = getCurrentPages(); prevPage = pages[pages.length-2];
-
跳转回上一个页面
wx.navigateBack({});
-
小程序页面的生命周期
-
onLoad(一次)
-
onShow(只要展示这个页面,就会自动加载)
-
onReady(一次)
-
onHide(每次页面隐藏就会自动加载,)
-
onUnload(卸载页面,小程序关闭)
-
小程序代码:
-
全局app.js
App({ /** * 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) */ onLaunch: function () { }, globalData:{ userInfo: null, } })
-
auth.js:
// pages/auth/auth.js // 获取公共的那个app var app = getApp(); Page({ /** * 页面的初始数据 */ data: { phone:"15131255089", 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'}); } } }) }, xxx:function(e){ console.log(e); }, 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(); // prevPage = pages[pages.length-2]; // 跳转会上一级页面 wx.navigateBack({}); } else { wx.showToast({ title: "登录失败", icon: 'none' }); } } }) }, /** * 点击登录(不推荐) */ onClickLogin:function(e){ 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){ // 初始化用户信息 wx.getUserInfo({ success:function(local){ console.log(local); app.initUserInfo(res.data.data, local.userInfo); } }) // var pages = getCurrentPages(); // prevPage = pages[pages.length-2]; // 跳转会上一级页面 wx.navigateBack({}); }else{ wx.showToast({ title: "登录失败", icon: 'none' }); } } }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
-
auth.wxml
<!--pages/login/login.wxml--> <view class="logo"> <image src='/static/images/icon/logo.png'></image> <text>交流交易社区</text> </view> <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>
-
home.js:
// pages/home/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 }) }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
-
Home.wxml:
<!--pages/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="/static/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> | <navigator url="/pages/auth/auth">注册</navigator> </view> </view> <view class="site">查看个人主页</view> </view> <view class="numbers"> <view class="row"> <text>0</text> <text>关注</text> </view> <view class="row"> <text>0</text> <text>粉丝</text> </view> <view class="row"> <text>0</text> <text>赞与收藏</text> </view> <view class="row"> <text>0</text> <text>好友动态</text> </view> </view> </view> <view class="middle-view"> <view class="item"> <image src="/static/images/icon/transaction_order1_icon_show.png"></image> <text>待支付</text> </view> <view class="item"> <image src="/static/images/icon/transaction_order2_icon_show.png"></image> <text>待支付</text> </view> <view class="item"> <image src="/static/images/icon/transaction_order3_icon_show.png"></image> <text>待支付</text> </view> <view class="item"> <image src="/static/images/icon/transaction_order4_icon_show.png"></image> <text>待支付</text> </view> <view class="item"> <image src="/static/images/icon/transaction_order5_icon_show.png"></image> <text>待支付</text> </view> </view> <view class="function-view"> <view class="row"> <view class="left">我的钱包</view> <view class="right"> <text>¥20</text> <image class="go-icon" src='/static/images/icon/to_icon_show_small.png'></image> </view> </view> <view class="row"> <view class="left">我的优惠券</view> <view class="right"> <text>暂无课用</text> <image class="go-icon" src='/static/images/icon/to_icon_show_small.png'></image> </view> </view> <view class="row"> <view class="left">领劵中心</view> <view class="right"> <text>你的福利都在这里</text> <image class="go-icon" src='/static/images/icon/to_icon_show_small.png'></image> </view> </view> </view> <view class="contact-view"> <button open-type="contact"> <image src="/static/images/icon/wechat_contact_icon_show.png"></image> </button> <button bindtap="onClickCall"> <image src="/static/images/icon/phone_contact_icon_show.png"></image> </button> </view> </view> <!-- <button bindtap="onClickLogin">登录</button> <button open-type="getUserInfo" bindgetuserinfo="getUserInfoFunction">获取信息</button> -->
-
wx:if指令
-
api代码:
Urls.py:
from django.conf.urls import url,include from django.contrib import admin from api import views urlpatterns = [ url(r'^login/', views.LoginView.as_view()), url(r'^message/', views.MessageView.as_view()), ]
View.py:
import re import random import uuid from rest_framework.views import APIView from rest_framework.response import Response from django_redis import get_redis_connection from api import models from utils.tencent.msg import send_message from api.serializer.account import MessageSerializer,LoginSerializer 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.把验证码+手机号保留(30s过期) """ result = send_message(phone,random_code) if not result: return Response({"status": False, 'message': '短信发送失败'}) """ print(random_code) """ # 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':'发送成功'}) 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_object,flag = models.UserInfo.objects.get_or_create(phone=phone) user_object.token = str(uuid.uuid4()) user_object.save() return Response({"status":True,"data":{"token":user_object.token,'phone':phone}})
Models.py:
from django.db import models 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)
utils/tencent/Msg.py:(对view中的代码进行整合)
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 def send_message(phone,random_code,template_id="516680"): """ 发送短信验证码 验证码发送到手机上,购买服务器进行发送短信:腾讯云 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(settings.TENCENT_SECRET_ID, settings.TENCENT_SECRET_KEY) 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 = template_id # 模板参数: 若无模板参数,则设置为空 req.TemplateParamSet = [random_code, ] resp = client.SendSms(req) # 输出json格式的字符串回包 if resp.SendStatusSet[0].Code == "Ok": return True except TencentCloudSDKException as err: pass
settings.py
""" Django settings for auction project. Generated by 'django-admin startproject' using Django 1.11.9. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ 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/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'kd5pg1d974=(ola9nwou2(sdo+&zix)$l+08_8-v6um^ic2($!' # 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', "rest_framework", 'api.apps.ApiConfig', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'auction.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'auction.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://192.168.16.86:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} # "PASSWORD": "密码", } } } # ############################# 腾讯云短信配置 ########################## TENCENT_SECRET_ID = "AKIDW3Rgszw84ylQxMzNn7KOJ6kFPSL5c5MU" TENCENT_SECRET_KEY = "GQSMXmtsjR0QhuIalzTp250nU6digZSD" TENCENT_CITY = "ap-guangzhou" TENCENT_APP_ID = "1400302209" TENCENT_SIGN = "Python之路"
Serializer/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
Serializer/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('手机格式错误')
Script/测试redis.py:
import redis conn = redis.Redis(host='192.168.16.86', port=6379) # conn.set('foo', 'Bar') # result = conn.get('+8615131255089') print(result) # conn.flushall() print(conn.keys())
作业
-
登录逻辑
-
对象存储上传文件:官方文档有代码。
-
表结构的设计(业务逻辑设计表结构)