day04 功能开发

发布时间 2023-09-14 00:52:23作者: 贰号猿

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, 
      }
    })
    View Code

     

  • 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 () {
    ​
      }
    })
    View Code

     

     

  • 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>
    ​
    ​
    View Code

     

     

  • 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 () {
    
      }
    })
     
    View Code

     

  • 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>
     -->
    View Code

     

  • 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 Code

     

    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}})
    View Code

     



    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)
    View Code

     

    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
    View Code

     

    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之路"
    View Code

     

    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
    View Code

     

    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('手机格式错误')
    View Code

     

    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())
    View Code

     

     

 

作业

  • 登录逻辑

  • 对象存储上传文件:官方文档有代码。

  • 表结构的设计(业务逻辑设计表结构)