分类目录归档:技术

WePy 整合云开发

由于工作上的原因,有需求要使用 WePy,刚好,有了云开发的契机,就决定研究一下。

初试 WePY

想要做云开发和 WePY,首先要先熟悉 WePY。 WePY 其实之前就听说过,不过自己一直没有使用,更多还是习惯用小程序原生写法来进行开发。

后续,也用过一些其他的框架,比如 MinUI 。

和 MinUI 相比,WePY 给我来说最大的感受是提供了 promise 的支持和 async/await 的支持,这个支持可以极大的改善 JavaScript 编程时遇到的 Callback Hell 的问题,可以让我们更加愉悦的开发。

此外,我比较看重的特性是支持外部的 NPM 包,支持 NPM 包意味着可以更好的使用 JavaScript 原本的工程化的产品,也是大大提升生产力的特性。不过,相比于 Promise 的支持和 async/await 的支持,这个如今已经被微信小程序官方团队所支持的 npm 也显得不那么重要了。

做一个 WePY 的 tempalte 吧!

在看文档时注意到,WePY 是使用 wepy init 来进行初始化的,而且可以使用 wepy list 来查看项目的模板。发现了其中有一个基于 ZanUI 的模板。

由于 ZanUI 及更名后的 VantUI 我都比较熟悉,所以从他下手,去研究一下如何制作 WePY 的模板。

复制 repo名/模板名,并在前面加上 github 的域名,就找到了对应的 repo 。

简单的看了一下项目的文件,发现其中并没有什么特殊的,需要指明初始化内容的文件,所以猜测大概率是不需要进行单独的配置的。

这方面 WePY 的文档做的不是很好,我在文档中并没有找到相关的说明。如果可以的话,希望开发者可以加一个功能,比如加上一个 .wepy-template ,可以在里面加入一些交互式的问题,从而来让用户设置一些内容。

在确定了模板其实并不困难以后,我就开始做自己的云开发模板了。

如何做一个模板

做一个模板只需要三步

  1. 下载官方的 empty 模板
  2. 加入你自己的代码
  3. 上传到 Github

说起来简单,不过,在实际制作时,还是应当注意一些问题:

  1. 初始化 Empty 模板后,尽快使用 git init 来初始化版本控制。这是因为云开发是与小程序的 AppID 进行绑定的,如果前期没有做好控制,容易出现后续将 AppID 加入了版本追踪。所以在一开始,我就将项目进行了初始化,方便后续的回溯。
  2. 上传到 Github 时,应当注意配置 Readme,在我之前的文章中,曾经提到过我们应当尽可能的交付一些产品给世界,好让世界根绝我们所交付的产品进行估值。特别是对于模板类型的 repo ,更是需要一个非常好的 Readme ,来引导别人去使用自己的模板
  3. 制作完成模板后,记得自己使用 wepy init 命令将整个过程完整的走一遍。确保你的模板是可用的。

在使用 WePY 过程中遇到的一些问题

CloudFunctionRoot 的设定问题

在做第一个版本的时候,我是将云函数的目录放在 src 目录下,后续,在运行时发现, WePY 会自动编译云函数的目录,导致出错。不得已,我将云函数的目录放置在了项目的根目录。

当时在研究这部分时,我试图去寻找 wepy.config.js文件的配置说明,希望找到一个 exclude 配置项目,来忽略一部分目录,可惜,我并没有找到。

WePY 下的一些定义问题

在使用原生写法的时候,我常常会在 Page({}) 函数外部放上一些引用。比如这样

const database = wx.cloud.database()
const storeCollection = database.collection('store')
Page({
    onReady():function(){
        // some code
    }
})

在 WePY 中我试图将代码放在 Page 实例的内部,不过会导致报错,因此,我不得不废弃这样的写法,改用 constructor 来实现。

 export default class Index extends wepy.page {
    constructor () {
      super()
      this.db = wx.cloud.database()
    }
    onLoad() {
      console.log('onLoad')
      console.log(this.db)
    }
}

对 getApp 报错

在 WePY 中,由于传承了 Vue 的思想,并没有提供对 getApp的 hack ,不过在实际的测试过程中,发现 getApp() 依然可用。

如果不引入 Redux 之类的状态管理工具的情况下,getApp() 的单例模式作为一个全局的 Bus 来进行数据的传递还是非常方便的。

关于 WePY 的其他

总的来说, WePY 是一个值得尝试的框架,单纯 Async/Await 的引入,可以让你的代码变得更加简洁易懂。特别是如果你的项目需要长期运转的情况下,整洁的代码会帮助你的项目成功。

此外,如果你很习惯于 Vue 的写法,那么 WePY 不容错过,computed 属性还是非常实用的。

小程序的 marker 无法触发 bindmarkertap 事件应该如何处理

TL;DR

如果想要 marker 可以响应 bindmarkertap 事件,需要设定 markerID,这一点文档中并没有标注。

具体情况

在开发美食地图时,出现了一个问题,marker 的点击总是不会触发 bindmarkertap 事件。

我的代码是这样写的

    store.get().then(res => {
      this.setData({
        stores: res.data,
        windowHeight: app.globalData.windowHeight,
      }, () => {
        wx.hideLoading();
        wx.showToast({
          title: '双指缩放可以调整地图可视区域,查看更多美食',
          icon: 'none'
        })
      })
    })
Code language: JavaScript (javascript)

在地图中去调用 stores. marker

在地图上的确可以出现图标,但是无法点击 maker 并触发事件。

通过复制官方的 marker 的数据进行调试后发现,当 maker 有 id 时,marker 就可以触发事件,因此,怀疑是 ID 的问题。

在美食地图小程序中,我使用的是腾讯云提供的小程序·云开发,其使用的是 MongoDB 作为后台的 Database ,默认的主键为 _id,所以,我自己写了代码来转换 _id

      data.map(item => {
        item.id = item._id
      });
Code language: JavaScript (javascript)

对应的 commit :https://github.com/CloudKits/miniprogram-foodmap/commit/5abcad1f756e03a388bb33dd1c699d3cae9ea0c4#diff-f5ea41cdd371d7b65bfdf8d32188e37d

实现密码生成器的思路

在项目中有这样一个需求:需要生成一组随机密码,确保安全性。因此,我去翻了一个项目的代码。这里是具体的代码

function GeneratePassword() {
    if (parseInt(navigator.appVersion) <= 3) {
        alert("Sorry this only works in 4.0+ browsers");
        return true;
    }
    var length=8;
    var sPassword = "";
    length = document.aForm.charLen.options[document.aForm.charLen.selectedIndex].value;
    var noPunction = (document.aForm.punc.checked);
    var randomLength = (document.aForm.rLen.checked);
    if (randomLength) {
        length = Math.random();
        length = parseInt(length * 100);
        length = (length % 7) + 6
    }
    for (i=0; i < length; i++) {
        numI = getRandomNum();
        if (noPunction) { while (checkPunc(numI)) { numI = getRandomNum(); } }
        sPassword = sPassword + String.fromCharCode(numI);
    }
    document.aForm.passField.value = sPassword
    return true;
}
function getRandomNum() {
    // between 0 - 1
    var rndNum = Math.random()
    // rndNum from 0 - 1000
    rndNum = parseInt(rndNum * 1000);
    // rndNum from 33 - 127
    rndNum = (rndNum % 94) + 33;
    return rndNum;
}
function checkPunc(num) {
    if ((num >=33) && (num <=47)) { return true; }
    if ((num >=58) && (num <=64)) { return true; }
    if ((num >=91) && (num <=96)) { return true; }
    if ((num >=123) && (num <=126)) { return true; }
    return false;
}

Code language: JavaScript (javascript)

上完代码,接下来来分析一下这段代码

首先,这段代码中有三个函数,其关系如下图所示

image
密码生成器各函数关系图

其中 getRandomNum 生成了一个范围在 33 – 127 的数字。这里是由于在后续生成随机字符串时,用的是 String.fromCharCode,当数字在 33~127 之间时,能够确保生成的数字在输入时比较简单。

image 1
String.fromCharCode 的测试结果

checkPunc 则是检测输出的文字是否可以直接发音,这涉及到记忆的难度的问题,在一个长的字符串中,记忆 ABCD 肯定比记忆逗号、破折号、句号等符号要简单一些,毕竟字母肯定要好记忆点,可以自编语句来实现。

接下来我们来看最核心的 GeneratorPassword 函数:如果将其中的 Dom 操作都忽略掉,并设置一个值。并进行配置后,代码如下:

function GeneratePassword() {
    // 检测浏览器支持
    if (parseInt(navigator.appVersion) <= 3) {
        alert("Sorry this only works in 4.0+ browsers");
        return true;
    }
    var length=8;
    var sPassword = "";
    length = 10 // 此处代码原为使用 dom 函数获取表单数据的代码,来实现自定义长度的功能。
    var noPunction = false// 此处代码原为获取表单数据的代码,控制是否检测密码的可读性
    var randomLength = false // 此处代码原为获取表单数据的代码,控制是否使用随机长度。
    // 如果使用随机长度的密码,则进入循环
    if (randomLength) {
        length = Math.random();
        length = parseInt(length * 100); // 生成 0 ~ 100 之间的数字
        length = (length % 7) + 6 // 对生成的长度取余,则最短为6,最长为12
    }
    // 根据长度进行循环
    for (i=0; i < length; i++) {
        numI = getRandomNum();  // 获取一个随机数
        if (noPunction) { while (checkPunc(numI)) { numI = getRandomNum(); } } // 如果需要判断发音,进行发音判断,判断失败就重新生成,直到成功。
        sPassword = sPassword + String.fromCharCode(numI); // 将生成的字符串加入到原有的字符串最后
    }
    // 设置代码到 Dom 元素中
    return true;
}
Code language: JavaScript (javascript)

其代码逻辑图如下

未命名表单 1
密码生成器的代码逻辑图

第三方服务前端数据安全校验

坐在腾大写博客,听到了身边的人在讨论前端数据安全校验的方案。

他们需要提供 H5 的SDK ,既然运行在前端,那么应用的 Secret 和 Key 就有可能暴露在浏览器里,如何确保 Secret 和 Key 的安全呢?

一个比较主流的方案就是使用白名单限制。每一个应用绑定特定的几个域名,只有白名单里的域名可以请求服务。

我还想到了另外一个案例。我自己比较喜欢使用 Algolia 提供的搜索服务,在 Algolia 中,区分 Master Key 和 Search Only Key。Search Only Key 只能用于数据查询,而 Master Key 可以用来添加数据。

如果是这样的话,或许可以比较好的处理。

如何保持对技术的敏感性?

我有个习惯:做 Side Project
我还有个习惯:每次做 Side Project,使用不同的技术。
举个例子:
我经常会做和 web 相关的项目,第一个项目我使用 Laravel (Php)开发;第二个项目我就考虑使用 Rails (Ruby)开发;第三个项目就用 Django(python) 开发。
这样、只要 Side Project 不死,我就需要经常维护,对我来说,就是保持自己一直掌握这些技能,不会丢掉技能。

Typecho 的所有挂载点

admin/write-post.php:58:                    content($post); ?>
admin/write-post.php:97:                        option($post); ?>
admin/write-post.php:140:                            advanceOption($post); ?>
admin/write-post.php:171:Typecho_Plugin::factory('admin/write-post.php')->trigger($plugged)->richEditor($post);
admin/write-post.php:178:Typecho_Plugin::factory('admin/write-post.php')->bottom($post);
admin/theme-editor.php:60:Typecho_Plugin::factory('admin/theme-editor.php')->bottom($files);
admin/editor-js.php:224:    markdownEditor($content); ?>
admin/menu.php:7:        navBar(); ?>
admin/header.php:15:$header = Typecho_Plugin::factory('admin/header.php')->header($header);
admin/footer.php:6:Typecho_Plugin::factory('admin/footer.php')->end();
admin/profile.php:58:Typecho_Plugin::factory('admin/profile.php')->bottom();
admin/common.php:18:Typecho_Plugin::factory('admin/common.php')->begin();
admin/write-page.php:53:                    content($page); ?>
admin/write-page.php:86:                        option($page); ?>
admin/write-page.php:112:                            advanceOption($page); ?>
admin/write-page.php:142:Typecho_Plugin::factory('admin/write-page.php')->trigger($plugged)->richEditor($page);
admin/write-page.php:149:Typecho_Plugin::factory('admin/write-page.php')->bottom($page);
admin/write-js.php:2:write(); ?>
index.php:20:Typecho_Plugin::factory('index.php')->begin();
index.php:26:Typecho_Plugin::factory('index.php')->end();
var/Widget/Upload.php:90:        $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasUploaded)->uploadHandle($file);
var/Widget/Upload.php:162:        $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasModified)->modifyHandle($content, $file);
var/Widget/Upload.php:227:        $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasDeleted)->deleteHandle($content);
var/Widget/Upload.php:245:        $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasPlugged)->attachmentHandle($content);
var/Widget/Upload.php:264:        $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasPlugged)->attachmentDataHandle($content);