小程序开发实践心得

简介: 这次参与了支付宝的《繁星计划*支付宝校园生活小程序大赛》,本篇文章整理了在开发过程中遇到的一些问题,解决方法和一些关于知识点的总结。

这次参与了支付宝的《繁星计划*支付宝校园生活小程序大赛》,本篇文章整理了在开发过程中遇到的一些问题,解决方法和一些关于知识点的总结。

遇到的问题和解决方法

对于小程序的安装了环境的配置就不多说了,由于这次开发的小程序是校园应用类的。不可避免的就需要用到校园账号的登录授权。

登录授权

我们采用Oauth2.0授权码模式authorization code),它是功能最完整、流程最严密的授权模式。具体流程如下图。
image-20200227141112679.png

小程序核心流程

本次小程序简化了以上的流程,通过一个接口封装了以下ABCD流程。

  • A. 访问业务服务器接口

该接口由第三方应用传入,在服务器中生成state并写入session,组装好授权URL,并重定向到授权URL

  • B. 访问授权页面

访问授权页面前需要先登录,未登录会重定向到登录页面,如果之前登陆过但没有进行授权,且认证服务器的登录态仍有效,则直接能访问授权页面

  • C. 重定向到指定url,并带上授权码code

重定向的URL是由第三方应用在调起浏览器过程中传入的,认证服务器会在URLquery中在加入code参数和state参数,重定向请求到第三方应用的业务后台,然后从query中取出授权码codestate,比对sessionstatequery中的state是否一致
(如果不一致,证明该请求不是来自需要授权的客户端,授权失败,可能遭到CSRF攻击)

  • D. code换取用去用户凭证token

业务后台拿到授权码后,需要请求认证服务器换取登录凭证tokentoken可用于调用开放平台所提供的服务

  • E. 授权成功,返回后台登录态skey

授权成功后,业务后台需要根据自身需求,管理用户的登录态,并给客户端返回自身后台的登录态

  • F. 获取业务后台登录态,退出浏览器

第三方应用客户端获取到登录态,并退出浏览器

授权成功后,后台将返回一个skey给前端,前端则需要将skey存储在本地,供以后各项功能的使用。

skey的作用

  • 用户只要输入一次校园网账号密码,以后进入小程序都不需要再次手动进行登录(除非登陆态过期失效)。
  • 用户在使用一些需要用到校园网账户身份的功能(比如查个人课表,查个人成绩)时,小程序前端就会将该用户的skey传入后台,假如该skey是有效的,那么后台可以根据skey找到用户的校园网账号user_id,然后再使用user_id去调用接口实现用户需求。在此处,skey的作用是将课程表用户身份转换为校园网用户身份

图片上传至七牛云对象存储

客户端上传前需要先从服务端获取上传凭证,并在上传资源时将上传凭证作为请求内容的一部分。后台获取token流程参考官方文档:七牛云上传凭证

此处主要是前端代码,以上传个人支付宝头像为例。

  1. 首先获取个人支付宝信息
my.getAuthCode({
    scopes: 'auth_user',
    fail: (error) => {
        console.error('getAuthCode', error)
    },
    success: () => {
        my.getAuthUserInfo({ // 获取支付宝个人信息
            fail: (error) => {
                console.error('getAuthUserInfo', error)
            },
            success: (res) => {
                that.downloadAvatar(res.avatar)
            }
        })
    }
})
  1. 获取个人头像后本地下载
// function downloadAvatar(avatar)
my.downloadFile({
    url: avatar,  
    success(res) {
        that.uploadQiniu(userInfo, res.apFilePath)
    }
})
  1. 获取上传到七牛云服务器的token(由后台返回)
getToken: function () {
        return request({
            url: api.get_avatar_token, // 后台返回token接口
            method: 'POST',
        }).then(res => {
            if (res.data.code !== '0') {
                throw new ErrorMessage('不能获取上传头像的token, 错误码为', res.data.code)
            }
            return Promise.resolve(res)
        })
},
  1. 将头像上传到七牛云
my.uploadFile({
            url: 'https://up-z2.qiniup.com/',
            fileType: 'image',
            fileName: 'file',
            filePath: tempFilePaths,
            header: {
                "Content-Type": "multipart/form-data"
            },
            formData: {
                file: tempFilePaths, // 刚才下载的头像图片路径
                key: res.data.expected_Key,
                token: res.data.token, // 后台返回的上传凭证
            },
            success: function (res) {
                const data = JSON.parse(res.data)
                // 成功上传
            },
            fail: function (res) {
                console.log(res)
            }
})

解析HTML并显示

在实现对别的网页里的文章的展示的时候,而此时后台返回的内容是一整个HTML时,我们知道虽然小程序有富文本组件rich-text,但其nodes 属性只支持使用 Array 类型。针对HTML字符流则需要自己将其转化为 nodes 数组,此时我们需要一个工具将HTML解析。这个工具就是mini-html-parser2

  1. 首先安装mini-html-parser2
npm install mini-html-parser2 --save
  1. 在项目里引入
import parse from 'mini-html-parser2'
  1. html字符串进行解析
// 传入的html为后台返回的html字符流
parse(html, (err, nodes) => {
    if (!err) {
        that.setData({
            article: {
                content: nodes
            }
        })
    }
})

// .axml页面
<rich-text nodes="{{ article.content }}"></rich-text>

格式化时间

有时候我们需要将当前日期2019\2\2格式化成2019-02-02,可以使用以下方法

dateFormly: function (seconds) {
    let date = (new Date(seconds)).toLocaleDateString()
    const [year, month, day] = date.split('/')
    return `${ year }-${ this.dataLeftCompleting(month) }-${ this.dataLeftCompleting(day) }`
},
// 数字补全
dataLeftCompleting: function (value) {
    return parseInt(value, 10) < 10 ? "0" + value : value
},

其他注意事项

  • <scroll-view>组件的使用需要给定高度height,不然无法触发onScrollToLower等边界事件
  • 将需要用到的域名添加到白名单!

自制工具包

工具包是一些封装过的小程序接口,作用是为了开发人员在写代码的时候可以只专注于前端页面的开发,省去一些不必要的逻辑和重复的代码。

接口Promise化

众所周知,promise是异步操作,可以把执行代码和处理结果的代码清晰地分离了。此处将请求的接口promise化,有利于防止回调地狱,增强代码的可读性。

const aliPromisify = function (fn) {
    return function (obj = {}) {
        return new Promise((resolve, reject) => {
            obj.success = function (res) {
                // 成功
                resolve(res, obj)
            }
            obj.fail = function (res) {
                // 失败
                reject(res)
            }
            fn(obj)
        })
    }
}

这里运用到了javascript的一个知识,即函数的返回值可以是函数

*稍微举个例子,不过多展开

function createCompare(propertyName){
    return function(ob1,ob2){
        return ob1[propertyName] < pb2[propertyName]
    }
}

var data = [{name:'aaa'}{name:'bbb'}]
data.sort(createCompare('name'))

然后将需要封装的接口抛出去供开发者使用。

const aliGetUserInfo = wxPromisify(my.getOpenUserInfo)
const aliRequest = wxPromisify(my.request)

export { aliPromisify, aliGetUserInfo, aliRequest }

自定义错误类

有时候因为各种各样的原因,比如请求出错,账号密码填错,后台问题等,需要将错误提示抛出展现给用户和开发人员。但一般来说用户不需要接触底层的错误,而开发人员则会想错误的根本来源是哪里。所以定义了一个类来展示错误信息。

class ErrorMessage {
    constructor(msg, code, options) {
        const error = new Error(msg)

        // 去掉该函数的错误栈
        error.stack = error.stack.replace(/\n.*\n/, '')
        let option = options
        if (typeof code === 'string') {
            error.message += `错误码: ${code}`
        } else {
            option = code
        }
        console.error(error)
        return error
    }
}

// 使用方法如下,在需要抛出错误的地方添加下面句子
throw new ErrorMessage('请求xxx接口出错', code)

知识点整理

this的指向

*this永远指向最后调用它的对象

  • 直接调用:fn则是普通函数,this指向全局对象window,匿名函数的this永远指向window

    var name = 'window'
    function a() {
        return function () {
            console.log(this.name)
        }
    }
    var b = a
    
    b.call({name:'111'})() // window , a的this现在指向111
    b().call({name:'111'}) // 111 , return函数的this指向111
  • new操作:fn就是构造函数,this指向新创建的对象
  • 对象调用:fn就是方法,this指向调用它的那个方法
  • 箭头函数:声明函数时所在上下文中的this。但这个this有时候也会改变,如指向构造函数的闭包

    var name = 'window'
    var person1 = {
        show2: () => console.log(this.name)
    }
    person1.show2() // window
    person1.show2.call({name:'111'}) // window

改变this指向

  • 箭头函数

    1. 箭头函数没有this,如果箭头函数被非箭包含,则this指向最近一层的非箭,否则为undefined(严格),window(非严格)
    2. 无法通过callapplybind绑定箭头函数的this
  • 在函数内部使用 that = this
  • 使用apply、call、bind
  • new实例化一个对象

构造函数

优点

  • 没有显示创建对象
  • 直接将变量和方法赋给this对象
  • 没有返回值

    1. 无返回值或者返回基本类型,则new 返回的是实例化对象
    2. 返回值是引用类型,则new返回的是该引用类型

缺点

每个方法都要在实例上重新创造一遍,即每个实例的方法指向的都不是同一个对象。

解决方法:使用原型模式

new的过程

  1. 创建一个新的对象
  2. 将构造函数的作用域赋给新对象(this指向新对象
  3. 执行构造函数的代码(将对象的__proto__指向原型的prototype)
  4. 返回对象

constructor属性:记录临时对象由哪个函数创建

如果不用new来创建,则作为普通函数调用,其属性会添加到全局变量中

原型对象

prototype是一个指针,指向函数的原型对象,可以访问通过构造函数创建的实例对象的原型对象。可以用来达到实例之间共享资源(属性和方法)

hasOwnProperty():检测一个属性是存在实例里还是原型里

person.prototype.isPrototypeOf(person1):确定括号里的对象是否是调用者的实例

Object.getPrototypeOf():取得对象的原型

实例化对象找不到属性回去原型对象(Object.prototype)去找

不能在实例上重写原型的属性,使用delete方法可以删除实例上的属性从而搜索原型上的同名属性

遍历属性

通过in返回true和hasOwnProperty返回fasle确定属性是在原型中

Object.keys():取得对象上所有可枚举的实例属性,接受一个对象,返回字符串数组

Object.getOwnPropertyNames():得到实例所有属性

以上两种方法可以用来代替for-in循环!!!

*注意

重写原型对象,其实例对象还是指向最初的原型

原型对象里的引用类型数据,如果通过实例对象对其进行修改,则所有实例对象都会修改

person1.friends.push('AA'); // person2.friends //'AA'

数据属性

特性

  • [[configurable]]:表示能否通过delete删除属性从而重新定义属性,能否把属性修改为访问器属性
  • [[Enumerable]]:表示能否通过for-in遍历
  • [[Writable]]:表示能否修改属性的值
  • [[Value]]:包含该属性的数据值

object.defineProperty():

  • 用法:修改以上特性。
  • 参数:属性所在对象、属性名、特性名
  • 注意:一旦把属性定义为不可配置(configurable:false),则不能再变回可配置

object.defineProperties():

  • 用法:通过描述符一次定义多个属性
  • 参数:属性所在对象、对前面对象属性一一对应的对象

object.getOwnPeopertyDescriptor():

  • 用法:取得给定属性的描述符
  • 参数:属性所在对象、属性名

访问器属性(不包含数据值)

特性

  • [[configurable]]:表示能否通过delete删除属性从而重新定义属性,能否把属性修改为访问器属性
  • [[Enumerable]]:表示能否通过for-in遍历
  • [[Get]]:在读取属性时调用的函数
  • [[Set]]:在写入属性时调用的函数

用途

设置一个值会导致另外的值变化

var food = {
    _fish: 10,
    vagetable: 2
}
Object.defineProperty(food, "dinner",{
    get: function(){
        return this._fish
    },// 将_fish的指针赋给dinner
    set: function(newValue){
        if(newValue > 5){
            this._fish = newValue
            this.vagetable += newValue - 4
        }
    }
})
food.dinner = 6 // dinner=6,vagetable=4
目录
相关文章
|
1月前
|
JSON 小程序 JavaScript
微信小程序入门实践
微信小程序入门实践
|
1月前
|
小程序 JavaScript 前端开发
微信小程序云开发入门实践
微信小程序云开发入门实践
|
7月前
|
编解码 小程序 JavaScript
阿里云IoT小程序应用开发和组件实践
通过实验,了解阿里云IoT小程序的应用开发的方法,了解其内置的基础组件使用,以及基于Vue.js实现可复用的自定义组件的方法。
341 1
|
10月前
|
JSON 小程序 前端开发
小程序长列表优化实践
小程序如何实现长列表优化呢
小程序长列表优化实践
|
10月前
|
Web App开发 移动开发 小程序
|
11月前
|
Web App开发 小程序 算法
大屏小程序探索实践 | Cube 技术解读
大屏小程序探索实践 | Cube 技术解读
183 0
|
小程序 JavaScript 前端开发
微信小程序:阿里云OSS直传实践-PHP实现服务端签名
微信小程序:阿里云OSS直传实践-PHP实现服务端签名
612 0
微信小程序:阿里云OSS直传实践-PHP实现服务端签名
|
存储 JSON 小程序
微信小程序开发实践
微信小程序开发实践
76 0
|
存储 SQL 运维
serverless 学习 | QCon2022-深圳: 50W+小程序开发者背后的数据库降本增效实践
serverless 学习 | QCon2022-深圳: 50W+小程序开发者背后的数据库降本增效实践
151 0
serverless 学习 | QCon2022-深圳: 50W+小程序开发者背后的数据库降本增效实践