小程序开发实践心得

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

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

遇到的问题和解决方法

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

登录授权

我们采用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
目录
相关文章
预约按摩小程序开发,为什么很多上门按摩平台根本招聘不到优秀技师?
上门按摩平台面临招不到优秀技师的问题,主要原因是平台众多,技师选择多样。为解决此问题,平台可引入技师等级制度,根据订单数量和好评率划分高、低等级技师。高等级技师可享受70%-90%的高提成及首页推荐,这不仅能激励技师的积极性,还能帮助平台筛选出优质技师,提升服务质量和口碑,形成良性循环。
|
23天前
|
人工智能 小程序
【一步步开发AI运动小程序】五、帧图像人体识别
随着AI技术的发展,阿里体育等公司推出的AI运动APP,如“乐动力”和“天天跳绳”,使云上运动会、线上健身等概念广受欢迎。本文将引导您从零开始开发一个AI运动小程序,使用“云智AI运动识别小程序插件”。文章分为四部分:初始化人体识别功能、调用人体识别功能、人体识别结果处理以及识别结果旋转矫正。下篇将继续介绍人体骨骼图绘制。
|
24天前
|
人工智能 小程序 vr&ar
AI运动小程序开发常见问题集锦二
截至当前,我们的AI运动识别小程序插件已迭代至第23个版本,广泛应用于健身、体育、体测、AR互动等场景。本文针对近期用户咨询,汇总了常见问题,帮助用户减少开发成本,提高效率。主要涵盖计时与计数模式的区别、综合排行榜生成方法、全屏模式适配及无开发能力用户的解决方案。
|
24天前
|
小程序 数据挖掘 UED
开发1个上门家政小程序APP系统,都有哪些功能?
在快节奏的现代生活中,家政服务已成为许多家庭的必需品。针对传统家政服务存在的问题,如服务质量不稳定、价格不透明等,我们历时两年开发了一套全新的上门家政系统。该系统通过完善信用体系、提供奖励机制、优化复购体验、多渠道推广和多样化盈利模式,解决了私单、复购、推广和盈利四大痛点,全面提升了服务质量和用户体验,旨在成为家政行业的领导者。
|
1月前
|
存储 传感器 小程序
跳绳计数小程序开发系统
首先,我们需要明确跳绳计数小程序的核心功能:为用户提供跳绳计数的便捷方式。这意味着小程序需要能够准确地记录用户的跳绳次数,并为用户提供清晰、直观的计数展示。
|
1月前
|
人工智能 编解码 小程序
【一步步开发AI运动小程序】四、小程序如何抽帧
随着AI技术的发展,阿里体育等公司推出的“乐动力”、“天天跳绳”等APP使云上运动会、线上健身等概念备受关注。本文将引导您从零开始开发一个AI运动小程序,利用“云智AI运动识别小程序插件”。文中详细介绍了微信小程序抽帧的相关API、设置及注意事项,帮助开发者更好地实现AI运动功能。下篇将介绍人体识别技术,敬请期待。
|
1月前
|
传感器 存储 小程序
跳绳计数小程序开发
跳绳计数小程序旨在通过智能设备(如手机或智能手表)记录用户在跳绳过程中的次数、时间、速度等关键数据,为用户提供便捷的运动记录和数据分析服务
|
1月前
|
小程序 搜索推荐 前端开发
小剧场短剧影视小程序开发
小剧场短剧影视小程序旨在为用户提供一个便捷、互动的平台,让用户能够随时随地观看、分享和评论各类小剧场短剧。通过小程序,用户可以浏览热门短剧、搜索感兴趣的内容、参与社区互动,以及享受个性化的推荐服务。
|
1月前
|
小程序 搜索推荐 前端开发
短剧小程序开发案例
首先,明确你的短剧平台的目标用户群体和他们的需求。比如,年轻用户可能更倾向于轻松、幽默的短剧内容,而家庭用户则可能更偏爱教育、亲子类的短剧。了解用户需求有助于你设计更符合他们口味的功能和界面
|
1月前
|
机器学习/深度学习 人工智能 小程序
【一步步开发AI运动小程序】三、运动识别处理流程
随着人工智能技术的发展,阿里体育等公司推出的“乐动力”、“天天跳绳”等AI运动APP备受关注。本文将引导您从零开始开发一个AI运动小程序,使用“云智AI运动识别小程序插件”。文章介绍了视频帧、帧率FPS、抽帧和人体识别等基本概念,并详细说明了处理流程,包括抽帧、人体识别检测、骨骼图绘制和运动分析等步骤。下篇将介绍如何在小程序中实现抽帧。
下一篇
无影云桌面