月度归档:2021年01月

building photography

Serverless 不是银弹

这张图相信大家在互联网上或多或少都看过,说的很显示,如果你想要好且快的东西,那就是一分钱一分货。如果你想要快且便宜的东西,那必然会变得丑一些。

图源:Facebook劍心日劇廣告娛樂情報

作为互联网从业者,一个打工人,相信我的读者们都能够想明白这个点。

不过,大家似乎在看待别人的产品,特别是一些大厂的产品时,并不能够认清这个事实

产品方出于产品推广和运营的需要,必然会在产品推广和实际介绍的时候,宣称自己可以做任何东西。但作为真正使用的用户,你必须明确的知道,任何产品都具有自己的问题,没有一个产品可以满足你的所有诉求

我们以互联网产业中最为常见的问题来说,现在大型厂商一般都会推出自己的传统主机计算业务和 Serverless 云服务。

主机计算服务可以提供独立的主机给你使用,但相应带来的问题是你需要自己构建一整套基础环境,并进行基础的环境运维。

Serverless 服务则可以为你提供更高的弹性和更简单的环境配置,但相应的,也会带来更高的价格。 Serverless 服务在实际使用过程中,同等业务量的售价是会更高的。

你们一定要清楚不同的技术的优势和劣势。如果一个技术只有优势没有劣势,那他只能是个骗子了。

当你搞清楚技术的优势和劣势以后,反而你会对于技术的使用更加的得心应手。你不会在一个可能很大的项目中使用很低廉的技术方案。也不会在一个长期小而美的技术中,应用一整套互联网架构。

每一个产品都有自己的优势和劣势,你理解了优势和劣势,再根据实际情况选择方案,才能够不给自己挖坑,不给自己的长期运转留问题。

微信生态中的 openId、unionID和业务系统中的ID

在进行微信生态相关的开发的时候,经常会遇到一个术语:openID。openID 在微信生态下几乎无处不在:

  • 你想要识别用户身份?需要 openID
  • 你需要给用户推送消息?需要 openID

除了 openID 以外,在开发时,还会有另外一个术语:unionID,当你思考多个微信生态下的应用互相协作之时,就会遇到 unionID。

此外,我们自己开发的业务系统中,还会有一个自己业务系统中的 ID,那如何理解这三个 ID 呢?

业务系统中的 ID

一般来说,我们会在自己的业务系统中存储一份用户信息,在存储用户信息的时候,用户信息会在数据库中有一个名为 ID 的主键,后续我们在进行各项开发的时候,都会使用这个 ID 来指代对应的用户。从而在开发过程中,随时可以获取到用户的一些信息。

我们会在这个 ID 对应的数据库记录中存储一些用户的数据,比如用户名、用户的头像、用户的昵称、用户的备注等等。

openID 微信应用中的用户唯一 ID

openID 是微信应用中的唯一 ID, 这里的微信应用指的是微信生态下的应用,不仅仅是小程序,也表示公众号、小商店等应用。

每一个用户在访问了某一个小程序后,就会获得自己在这个小程序中的唯一 ID。这个唯一 ID 和两个因素有关:是哪个小程序?是哪个用户?

也正是因为这两个因素,导致同一个用户在不同的小程序中的 openID 不同,所以说,openID 是微信应用中的用户唯一 ID,也仅在这个场景中有用。你在小程序拿到一个用户的 openID,意味着它只能在这个小程序中用于定位某个用户,你把这个 openID 拿到另外一个小程序/公众号中去使用,就无法定位到当前的用户。

unionID 微信应用群中的用户唯一 ID

微信生态下除了小程序,还有订阅号、服务号。你肯定希望自己的订阅号、服务号、小程序可以共用一套用户系统,这样用户在进入这三处地方,就可以获得一致的体验,比如订阅号的用户在进入小程序后,可以看到自己在订阅号中做过的事情。

这个时候,你就需要借助微信应用群中的用户唯一 ID —— unionID。

这里需要注意关键词「微信应用群」。你的公众号和你的小程序并不是天然成为一个微信应用群的。想要达成一个微信应用群,你需要在微信开放平台将你的公众号和小程序之间进行关联。在完成关联以后,微信就会在符合要求的情况下,给予每次请求赋予一个 unionID 的属性。

微信开放平台

具体的要求如下:

  1. 调用接口 wx.getUserInfo,从解密数据中获取 UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。
  2. 如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过 wx.login + code2Session 获取到该用户 UnionID,无须用户再次授权。
  3. 如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过 wx.login + code2Session 获取到该用户 UnionID ,无须用户再次授权。
  4. 用户在小程序(暂不支持小游戏)中支付完成后,开发者可以直接通过getPaidUnionId接口获取该用户的 UnionID,无需用户授权。注意:本接口仅在用户支付完成后的5分钟内有效,请开发者妥善处理。
  5. 小程序端调用云函数时,如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号,可在云函数中通过 cloud.getWXContext 获取 UnionID。
  6. 小程序端调用云函数时,如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用,也可在云函数中通过 cloud.getWXContext 获取 UnionID。

如何使用 openID、unionID 和 ID

如果你希望你的业务可以实现,在 A 中的操作,可以在 B 中看到,那么你就需要借助于 unionID,将不同的用户关联起来,在数据库设计方面,你可以在用户表中设定几个属性: application_a_openid 、application_b_openid 和 unionid。

在执行查询的时候,判断是否存在对应 unionId 的用户,并进行关联。

总结

微信开发生态中有不少的 ID,看似纷乱,但如果你可以把他们以某一个特定的场景来思考,就会更好理解。

wxa.js 开启极致压缩

在之前的小程序性能优化系列中,我给出了如何分析文件大小和压缩图片的方式。但在一个项目中,如果我们已经完成了相关的文件压缩以后,还有没有办法进一步压缩呢?

答案是有的,你除了可以压缩图片以外,还可以选择压缩项目中的代码。

而这些部分,你可以借助一些工具来完成代码的压缩,其中包括:

  • uglifyjs: 压缩项目 JS 代码
  • html-minifier: 压缩项目 WXML 代码

在一个普通的小程序项目中,你需要自行编辑相关的依赖。而如果你使用的是 wxa.js,则可以使用官方提供的插件,十分简单的在你的项目中加入相关特性。

如何使用?

wxa.js官方提供了两个插件 @wxa/plugin-uglifyjs@wxa/plugin-minify-wxml, 只需要安装相关的插件,并在配置文件中引入,既可以在构建时加入代码压缩。

同时,为了方便,我们可以仅在进行生产环境构建的时候,从而实现开发的时候可以方便调试。

配置方法

首先,执行命令安装插件

npm i -D @wxa/plugin-minify-wxml
npm i -D @wxa/plugin-uglifyjs

其次,在 wxa.config.js 中添加配置项目

const UglifyjsPlugin = require('@wxa/plugin-uglifyjs');
const MinifyWxmlPlugin = require('@wxa/plugin-minify-wxml');
const prod = process.env.NODE_ENV === 'production';
// 其他配置代码
if (prod) {
    module.exports.plugins.push(new UglifyjsPlugin());
    module.exports.plugins.push(new MinifyWxmlPlugin());
}

这样,就可以限制在仅在编译生产环境代码时,对代码执行压缩。缩小项目体积。

wxa.js 如何使用 replace 插件实现版本号的方便更新

小程序在开发过程中,如果可以在应用的某个地方加入版本号的显示,可以在后续 debug 的过程中,快速的定位代码的版本。但在过去的开发过程中,我大多是手动修改版本号,这在实际的使用过程中,经常会出现忘记修改版本号,或者代码中的版本号和实际在 git 中记录的版本号不同,给自己在后续排除错误的时候增加困难。

不过,如果你在使用 wxa.js 的话,可以借助于 wxa.js 的 replace 插件来简化这个过程,降低工作量。

实现思路

npm 项目在其 package.json 中是有一个 version 字段的,我们可以借助 npm 自带的 version 功能,来实现版本的自增,并自动打上 git 的 tag,简化开发工作。

具体实现

想要实现自动管理控制,你需要在你的 wxa 项目中安装其 replace 插件

npm i -S @wxa/plugin-replace

安装完成插件后,在wxa.config.js中引入 package.json,并添加一个replace 规则对象。

const ReplacePlugin = require('@wxa/plugin-replace');
const package = require("./package.json")
module.exports = {
    plugins: [
        // 一个规则对象,key 为目标字符串,value 为替换内容
        new ReplacePlugin({
          list: {
            'VERSION': package.version
          }
        })
    ]
}

添加完成配置后,你就可以在你的小程序中的任何需要展示项目版本号的位置,新增一个 VERSION 字符串。后续在小程序开发过程中,这个版本号就会被替换为 package.json 中的版本号。

在后续开发时,当你完成了一个版本的开发时,就可以使用 npm version 命令来发布新的版本。

npm version 命令常用的版本变更命令包括:

  • npm version patch : 变更 patch 版本号,比如 1.0.0 变为 1.0.1
  • npm version minor : 变更 minor 版本号,比如 1.0.0 变为 1.1.0
  • npm version major : 变更 major 版本号,比如 1.0.0 变为 2.0.0
  • 更多的命令可以访问 https://docs.npmjs.com/cli/v6/commands/npm-version 查看

执行了版本变更命令后,npm 会自动更新 package.json 中的版本号相关字段,并自动执行 commit & 执行 git tag 命令。

如果你需要自定义 git message ,可以在执行命令时,加入 -m 参数,npm 会自动把版本号传递给 %s 字符串从而实现自定义的版本变更,比如 npm version patch -m "Upgrade to %s for reasons"

总结

wxa.js 提供的 replace 插件,可以帮助我们在开发过程中,通过简单的文本替换来实现一些简化工作流的功能, 如果你在使用 wxa.js 开发,不妨试试这个小技巧。

red and white nescafe ceramic mug

如何对项目中的图片进行压缩

在昨天的文章中,我们找到了项目中的大文件是什么,而大多数时候,你会发现项目中的大文件都是图片,只要对图片压缩一下,就可以轻松获得空间的释放。

为什么图片可以被压缩?

图片记录的信息包括颜色和坐标,而颜色会有很多是相同的。通过对于相同颜色可以进行合并处理。此外,图片压缩软件还会去除图片中的一些冗余信息,让空间只为必须的资源所用。

因此, 我们可以借助一些手段,来压缩项目中的图片,快速释放项目空间,为项目的代码留出空间。

如何批量对图片进行压缩?

其实互联网上一直都有不少的网站可以很好的完成对图片的压缩,比如 tinypng.org

tinypng.org

但这些网站的问题是一方面需要依赖网络,另一方面是对于项目的图片有限制,比如 tinypng 一次只能压缩 20 张图片,在你实际进行压缩的时候,就会遭遇项目中的文件太多,无法一次性压缩完成。

因此,这篇文章中,我会用一款免费软件来完成压缩 —— 图压

图压

图压支持 Windows 和 macOS 操作系统,你可以在你的日常开发环境中安装它,用来压缩项目中的图片。

你可以下载并安装图压,将项目中的图片文件拖入图压,就可以对图片进行压缩。

操作示意图

需要注意的是,图压默认并不会覆盖你的文件,而是在你的项目中生成原文件名-tuya的新图片,如果你需要覆盖图片,则需要点击下方的更多设置,在保存路径中,选择覆盖原文件。

压缩效果

就压缩效果而言,对于图片几乎可以实现 60% ~ 70% 左右的压缩,效果可以说是很不错了。对于一些图片特别多的项目,单纯图片压缩,就可以为项目节省 30% 左右的空间,还是非常可观的。

总结

图压是一个很好用的图片压缩软件,你可以在开发的时候,借助图压对项目中的图片进行压缩,从而实现优化项目的体积,让小程序的打开更加迅速

如何找出小程序项目中体积最大的文件?

为什么要找到小程序项目中体积最大的文件?

微信小程序由于有「用完即走」的愿景,在小程序的大小上做了一些限制,单个小程序的大小需要在 2M 以内,如果小程序大于2M,则需要通过分包来实现。

在不使用分包的情况下,想要确保小程序的大小符合要求,就需要对项目中的文件进行优化。通过找到大文件,对项目的大文件进行优化是一个好办法。

如何找到项目中的大文件?

在 macOS 系统中,你可以借助命令行工具,来快速找到项目中的大文件。

你可以打开命令行,输入以下命令,获取到项目中最大的 10 个文件。

find . -type f -not -path '**/.git/**'  -exec du -h {} + | sort -r -h | head -n 10

执行效果如下

命令行执行效果

代码解读

上面这行命令采用了 Linux 下的通道来进行数据的传递,可以分为以下三个子命令

  1. find . -type f -not -path '**/.git/**' -exec du -h {} + 找到当前目录下的所有文件,并执行 du -h 获取到文件尺寸
  2. sort -r -h 对输入的内容,降序排列
  3. head -n 10 提取前 10 条数据,进行展示

通过对于上述的三个子命令的理解,你可以根据自己的实际需求进行调整,适配你自己的项目情况。

building photography

平台型 Serverless 产品的必杀技:免鉴权调用API

我在2019年的文章中(别找了,被我删了)曾经介绍过,国内的 Serverless 产品可以分为两个大类:

  1. 大公司产品:包括腾讯云云开发、字节跳动微服务、阿里云的云开发产品、Google 的 Firebase
  2. 小公司产品:比如 LeanCloud、Bmob 等等

而前者的最佳发展路线我也曾经提到过,大公司的 Serverless 想要赢得时间的最佳方案,是通过内部资源的整合,将开发者彻底绑架在自己的平台战车之上

而这样的方案,现在已经在各家的方案中有所体现,比如腾讯云云开发和微信合作推出的「小程序·云开发」产品中的「云调用」能力、轻服务推出的免鉴权调用小程序 API 的能力。

在微信小程序中,你可以通过 cloud.openapi.templateMessage.send 来实现免鉴权调用公众号的模板消息 API ,将过去数百行的代码简化为一行代码实现,可以有效的提升开发者的开发效率。

如今,轻服务提供了类似的功能,你可以通过 inspirecloud.openapi.tt.sendTemplateMessage实现免鉴权调用推送模板消息。

我一直觉得,Serverless 这样的产品,是为小团队、创意者而生的。而这些人的特点是,对于技术的深度可能不那么在意,毕竟他们关注的是我要实现一个创意,解决一个问题,至于 Scale 的问题,是在后续才需要注意的。而对于大公司的产品来说,想要干掉小公司的产品,一个利器就是免鉴权调用平台 API

免鉴权调用平台 API 对于开发者而言是一杯毒酒,不喝,你可能会被竞争对手超越,最终失去用户而死;喝了,你就会因为 Serverless 这样的产品模式,最终被绑死在战车之上。随之而来的,便是伴随着产品用户规模的不断提升的,是这些 Serverless 基础设施的成本就会逐渐超过你使用传统的服务器模式的成本。

但回过头来说,你真的能拒绝这样的平台优势么?很难,即使作为我这样的前后端一把梭的开发者,也很难抵挡 Serverless 产品前期的低成本和快速迭代,对于大量无法独立完成大型后端开发的工程师来说,这是一个必须喝下去的毒酒。

希望你在选择技术方案的同时,不给自己挖坑。

blue and red cargo ship on sea during daytime

用 Docker 调试 Nginx

容器技术被广泛应用在各种场景,在实际的应用过程中,我们也可以根据自己的需要,进行各种配置。我最近因为在调试 Nginx ,因此,也使用 Docker 来调试 Nginx。

Requirements

  • 已经安装 Docker
  • 安装了 docker-compose

实现思路

docker-compose 可以帮助我们把一些 Docker 启动的配置给简化,通过编写配置文件,简化启动容器的命令。

具体操作

创建一个项目,并在项目的根目录中放置 docker-compose.yml 以及 nginx.conf。其中,nginx.conf 是你需要测试的 nginx 文件,docker-compose.yml 则是用来记录你的 Docker 容器启动配置。

文件结构

启动容器并测试效果

将你需要测试的配置文件内容放置在 nginx.conf 中,并在dockcer-compose.yml 中添加如下内容

web:
  image: nginx
  ports:
    - "8080:80"
  volumes:
    - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
  command: [nginx-debug, '-g', 'daemon off;']

随后,执行 docker-compose up 就可以启动容器,并展示出容器的运行效果

执行效果

此时,你就可以访问 localhost:8080 来查看你自己的配置了。

building photography

平台型 Serverless 如何赋能中小开发者?

1 月 7 日的推文中,我有提到,「大公司的 Serverless 想要赢得时间的最佳方案,是通过内部资源的整合,将开发者彻底绑架在自己的平台战车之上」,对于很多人来说,如何理解这种内部资源的整合和它带来的赋能开发者?今天就用小程序 · 云开发最近开放的一个新能力 —— 短信跳小程序为例来聊一聊。

短信跳小程序这个能力其实背后是另外三个能力的组合:

  1. 微信最近开放的 URL Scheme ,我已经在 1 月 9 日的推文中写到了。
  2. 云开发的网站托管能力
  3. 腾讯云自有的短信推送能力

云开发基于上述的两个能力,辅以一些很低的开发工作量,实现了短信跳小程序这个能力。

其中,云开发的网站托管能力和短信推送能力,便是平台型 Serverless 产品内部资源整合而来的。

同样是一个 Serverless 团队要做这样的功能,作为小公司的 Serverless 就会面临:

  1. 短信服务供应商的选择和沟通
  2. 短信签名的审核和提交
  3. 底层代码拉通

但大公司的平台内往往已经自有短信推送的渠道,平台型 Serverless 只需要将底层代码拉通,就可以完成产品能力的对接。

这一点相比于小团队的重新选择供应商等,流程和耗时要少的多,要踩的坑也少的多

平台型 Serverless 团队借助于这样的很多内部能力,就可以轻松实现各种各样的能力,让开发者可以以最低的成本在自己的业务系统中加入产品能力。实际发布给开发者使用的产品,也就变得更加的简单易用。

就短信触达用户而言,云开发将这个能力做到了极简,你需要做的仅仅是将模板代码上传到静态网站托管中,并部署一个配套的云函数,就可以用cloud.openapi.cloudbase.sendSms触达你的用户,剩下的无需你操心,就自动完成了相关的功能。

const cloud = require('wx-server-sdk')
cloud.init()
exports.main = async (event, context) => {
  try {
    const result = await cloud.openapi.cloudbase.sendSms({
        env: 'online-12345678910',
        content: '发布了新的能力',
        path: '/index.html',
        phoneNumberList: [
          "+8612345678910"
        ]
      })
    return result
  } catch (err) {
    return err
  }
}

作为一个云开发的用户,我很期待有更多的同类型功能出现。

用 KOA 做 API Mock

在测试一些服务的时候,会需要用到一些第三方 API, 但如果你在测试的时候需要调用这个 API 的同时,又不太关注这个 API 具体的返回值的时候(比如你要测一个功能,但这个功能依赖了一个第三方服务,这个服务的返回值并不是你所关注的)。你需要一个比较好用的 Mock 方案,来解决这个问题。

写一个 Mock 服务并不是很复杂,想要写好的话,又不是一个很简单的事情。因此,各种语言提供的一些轻框架就是一个不错的选择。比如说我比较常用的是 KOA。

Koa 是 Node.js 下的一个轻量级框架,你可以通过简单的几行代码,构建一个简单的 HTTP 服务。

比如说我测试的服务依赖了两个第三方服务,这个时候我就可以借助于 Koa 提供的基础能力,快速编写出两个 API,从而在本地构建一个高性能的 Mock 服务。

比如说,这是我的一个 Mock 服务的代码案例。

其中我只使用了一个中间件,在中间件中使用 startsWith 来匹配请求路径,从而接管该路径下的所有请求,实现针对同一个 API 的所有请求返回一个特定的结果。

const Koa = require('koa');
const app = new Koa();
// response
app.use(async ctx => {
    ctx.body = "ok"
    if(ctx.url.startsWith("/api/xxx/")){
        ctx.body = 'need return2';
    }
    if(ctx.url.startsWith("/v2/xxx/")){
        ctx.body = 'need return 1'
    }
});
app.listen(3000);

如果你希望这个方案进一步优化,则可以考虑在返回结果时,给出一组数据,使用 Random 来在每一次返回时给出一个随机值,确保返回的数据不是唯一的。