- 小白变大神一:初识云开发数据库
《小白变大神,微信小程序云开发快速入门与成本控制实战》系列文章 第一篇:初识云开发数据库 文章系列介绍 上一篇文章发布后,收到许多网友的反馈,说是云开发很好用,就是资源消耗费用太高,用不起,用不起。 根据我自己的经验,云开发在使用时,如果能尽可能的控制好调用次数的资源消耗,这个费用是能接受的,但许多开发者对于资源消耗的控制并不是很好,导致资源消耗成指数增长,费用过高。这里有两方面的原因: 第一个原因是许多开发者是打工心态,开发费用的成本是公司承担,不是自己承担。就算自己花时间优化了资源消耗的调用,公司领导也看不到他的工作价值,然而自己也得不到什么好处。再加上领导不停地催促新功能的上线,加班频繁,时间压力也大,因此开发者往往不会花太多时间在资源消耗的优化上。 第二个原因是许多开发者已经养成了一定的编码习惯,在切换到云开发后会保持原有的编码习惯,也不太愿意花太多的时间去研究如何针对云开发优化代码。 在我学习并使用云开发的过程中,我也踩过许多坑,同时也积累了一些经验和代码库,这些代码库已经针对云开发优化了资源消耗的问题,例如可以仅消耗一次调用次数就能读取上万条数据,或者通过前端缓存减少数据库的读取次数等。 鉴于此,我决定把自己的经验分享出来,写成一个小系列文章。这个系列文章有三个目的: 引导新手快速入门外。如果你有一定的编程经验,但是对云开发不太熟悉,那么本教程能帮助你快速上手云开发; 分享代码库。让大家拿着现成的代码库就能用于自己的项目,而这些代码库已经充分的优化了调用次数的消耗问题,帮你控制成本。装备我给你的代码库,你瞬间就能从小白变大神; 把我在使用云开发的过程中踩过的坑和经验分享给大家,让大家少走弯路,节约时间。 因此,本系列文章的读者需要满足这三个条件: 有一定的编程经验,熟悉JavaScript语言; 对云开发的概念有一定的了解,包含云开发的原理、优缺点、费用等,如果你还不了解可以看我的上一篇文章; 对云开发还不太熟悉,想要快速上手。或正在使用云开发,但觉得云开发费用过高,想要降低成本。 新建云开发环境 假设你已经注册了一个微信小程序账号,并且已经购买了云开发环境(个人版19.9元每月),我们从新建项目开始。 如下图所示,请把项目名称改成你自己的项目名称,并填入你自己申请的小程序账号的AppID,然后选择 “微信云开发” -> “云开发基础模板”,开启我们的学习之旅。 [图片] 新建项目后,默认会有 cloudfunctions/ 和 miniprogram/ 两个目录,其中 cloudfunctions/ 目录是用于存放云函数的,miniprogram/ 目录是用于存放小程序前端代码的。 在新建的项目中会有一些默认的demo代码,我们并不需要这些demo,请根据下面的步骤删除: 删除 cloudfunctions/ 目录下的所有云函数,我们会在后面的文章中自己创建云函数。 删除 miniprogram/components/ 目录,这是demo页面中的组件。 删除 miniprogram/pages/ 目录下的所有文件,我们会自己创建页面。 [图片] 然后,在pages目录上点击右键,选择“新建文件夹”,输入index,新建一个名为index的目录。 [图片] 然后,在index目录上点击右键,选择“新建Page”,再次输入index,新建一个名为index的页面,此时会得到index.wxml、index.wxss、index.json、index.js四个文件。 [图片] 然后用同样的方法再新建一个“todo”页面,我们后面会在这个页面上实现一个todolist应用。 最后,把app.json文件修改成截图中所示样子,在 navigationBarTitleText 这里请输入你自己的项目名称。 [图片] 好了,这样我们就得到了一个云开发空项目,接下来我们先学习云数据库的使用。 初次使用云数据库 我们通过开发一个非常简单todolist页面来学习云数据库,页面中会有“增加todo”、“删除todo”、“完成todo”三项功能。 云开发环境虽然也支持MySQL关系型数据库(需单独购买),但是我并不推荐使用MySQL,因此本教程中我们只使用云开发自带的云数据库,这是一个类似MangoDB的文档型数据库。 首先需要一个数据库表(或称集合,以后将混用“表”和“集合”,它们同指一个意思)来存储todo数据,根据下图所示,我们在云开发控制台中新建一个名为todo的集合。 [图片] 然后我们在微信开发者工具(以后简称“工具”)中点击“编译”左边的下拉箭头,如图新建一个编译模式,然后点击“编译”按钮时就能刷新 pages/todo/todo 页面。 [图片] 写入第一条数据 把 pages/todo/todo.wxml 文件替换成下面的代码: [代码]<text>pages/todo/todo.wxml</text> <button type="primary" bind:tap="test1">写入第一个数据</button> [代码] 然后把 pages/todo/todo.js 文件替换成下面的代码: [代码]'use strict' Page({ data: { }, onLoad(options) { }, async test1 (e) { getApp().cloud.database().collection('todo').add({ data: { title: '学习云数据库', created: new Date(), } }) .then(res => { console.log(res) }) }, }) [代码] 点击工具的编译按钮(以后修改代码后可能需要重新编译,我将不再提醒),然后点击页面中的“写入第一个数据”按钮,你会在控制台中看到类似下图的输出(_id是随机生成的): [图片] 然后,你可以在云开发控制台中的todo表看见一条数据: [图片] 注意:以后修改数据后都需要手动点击刷新,我将不再提醒。 好了,通过上面4个步骤,我们完成了第一条数据的写入。 这里,我假设你已经熟悉JavaScript的 async/await 语法和Promise对象,如果你不熟悉,建议先自学一下。 然后我们来看看这一句写入代码: [代码]getApp().cloud.database().collection('todo').add({ data: { title: '学习云数据库', created: new Date(), } }) [代码] getAPP是一个全局函数,通过getApp().cloud获取到了云开发的全局对象(云开发的api都在这个cloud对象中),然后通过database()获取到了数据库对象。 collection(‘todo’)获得todo的集合对象,最后通过add()方法向集合中添加了一条数据,可参考add函数官方文档。 到这里,你已经成功向云数据库中写入了一条数据,注意这个数据并不是存储在你的电脑上,而是存储在微信云端服务器上。正如我上一篇文章所说,你不需要关心数据库在哪里、如何连接数据库、数据库的账号密码等(当然,你得有网),你只需要调用云开发的API就能操作数据库。 关注调用次数 在你刚才点击按钮向数据库写入数据时,每点击一次就会消耗一次调用次数,在基础套餐中每月有20万次调用次数资源可用,超过的部分需按照每万次0.5元的价格额外付费(购买资源包会更便宜)。 点击了解云开发的基础费用,以及高级套餐和资源包价格。 在本系列教程中,我会特别关注云开发费用问题,而调用次数的消耗又是导致成本过高的主要原因,因此我会在以后的文章中反复关注调用次数的消耗,并给出降低调用次数的方法和代码库。 自动生成的 _id 与 _openid 上面的写入代码中,我们只指明了title和created两个字段,但是在写入数据库后,我们会发现多出 _id 和 _openid 两个字段(如下图),这是云数据库自动生成的,其中 _id 是每条数据的唯一标识,_openid是写入数据的用户的openid(这里对应你扫码登录工具的微信账号)。 [图片] 在前端(前端指微信小程序的开发版本、体验版本、正式版本,而不包含云函数,以后同),我们可以自己指定_id字段,但**_openid字段是不可指定的**,_openid表示这一条数据是由哪个用户写入的,假如可以自己指定 _openid 字段,那么就可以修改 “别人的数据”,这是不符合逻辑的,也是不安全的。 另外,当我们去读取或修改数据时,如果数据库权限设置了“仅创建者可读写”,那么系统(“系统”指微信提供的这一套开发和运行环境,以后同)会自动根据当前用户的_openid去判断是否有读取或修改权限,这也是为什么系统不允许自定义_openid字段的原因。 注意:这里的 _id 和 _openid 必须有下划线。 和登录页面说拜拜 发现没有,当我们写入数据时,系统会自动强制写入_openid字段,而当我们读取、修改、删除数据时,又会根据_openid字段去判断是否有权限,这样系统就把数据与用户之间的关系建立起来了,并且没有登录步骤,也不需要事先调用任何接口获取用户的openid。 站在用户的角度,用户不需要任何登录页面和登录步骤,用户可以打开小程序就直接使用,并创建和维护自己的数据。 这里说的强制写入_openid仅限于前端,在后端(“后端”一词指云函数,以后将混用“后端”和“云函数”)并不会自动写入_openid字段,并且在云函数中也不需要考虑数据库权限问题,你可以理解成云函数的代码拥有所有权限。 用户在不同小程序中的_openid不同 在上一篇文章中我有提到,云开发环境可以共享给多个小程序使用(最多共享给10个小程序,且小程序需为同一个主体)。但注意,同一个用户在不同的小程序中_openid是不同的,如果你需要在不同的小程序中识别出他们是同一个用户(同一个微信账号),那么你需要使用UnionID字段,但本教程不涉及UnionID的讨论,你可以自行查阅微信官方文档。 了解数据库权限 新、旧两种权限设置 前面说到系统会自动根据_openid字段去判断是否有权限,这是因为新建表时系统默认设置了“仅创建者可读写”权限,如图所示: [图片] 如果点击“自定义安全规则”,则会弹出一个权限设置对话框,如下图所示: [图片] 我们在自定义安全规则输入框中看见有如下的默认规则: [代码]{ "read": "doc._openid == auth.openid", "write": "doc._openid == auth.openid" } [代码] 这里的doc表示集合中的数据,auth表示访问数据库的用户。 从字面意思上看,上面的规则也是“仅创建者可读写”的意思。但是,这里有一个新手都会踩的坑,这两种权限在实际使用中是有区别的。 先说结论,请不要使用前面四种权限设置,一律使用“自定义安全规则”,然后再来说说为什么。 [图片] 上图中的四种权限设置都是旧版本的权限设置,而下面的“自定义安全规则”是新的权限设置。虽然从字面上理解上面两种设置都是“仅创建者可读写”的意思,但是在系统内部,微信对两种权限的处理是不同的,这主要体现在: 当你使用旧的权限设置时,微信会自动为你的查询语句增加 _openid: ‘{openid}’ 条件,假设你有这样一句查询代码: [代码]// 假设已存在数据库db对象 db.collection('todo').where({ title: '我要学习' }).get() [代码] 但在实际执行时,微信会自动把这句代码转换成: [代码]db.collection('todo').where({ title: '我要学习', _openid: '{openid}' }).get() [代码] 这里的 ‘{openid}’ 是指当前用户的openid,对应数据库权限的 auth.openid,这样就实现了前面说的仅能读取自己的数据,避免了读取他人数据的安全问题。 注意:仅在设置仅用户可读或可写时,系统才会添加 _openid: ‘{openid}’ 条件,在设置 所有用户可读或可写时,系统不会添加 _openid: ‘{openid}’ 条件。 当你使用新的权限设置时,微信并不会自动帮你添加 _openid: ‘{openid}’ 条件,上面的读取代码会抛出没有权限的错误,如图所示: [图片] 提醒:你以后可能会经常遇到-502003 database permission denied这个错误,不妨现在记录到你的笔记本中,下次遇到了就先排查一下是不是权限设置引起的。 请一律使用新版权限设置 那为什么不直接使用旧的权限设置呢?反正我的小程序功能很简单啊? 哪怕你的小程序功能很简单,也不要使用旧版本,有这么几个原因: 旧的权限设置只有4种基本权限,这明显是不够用的,而新的权限设置可以根据自己的需求设置更复杂的规则。 旧的权限设置,如果你使用“所有用户可读”,此时微信不会自动帮你添加“_openid: ‘{openid}’”条件,这样就会导致一些表会自动添加“_openid: ‘{openid}’”语句,而一些表又不会自动添加,这会导致你的代码行为不统一,不好维护。 当你修改表权限设置时,系统会增加或删除“_openid: ‘{openid}’”条件,你的代码很容易产生bug。 当你某一张表需要使用新版本时,就会出现新、旧两种版本混用的情况,这绝对是噩梦。 因此,在本系列教程中,我们一律使用新的权限设置,即“自定义安全规则”,我不再重复提醒。 权限的 “覆盖原则” 使用新的权限设置后,只要你的查询语句不符合权限设置的“覆盖原则”,就会抛出上图中的 -502003 database permission denied错误。 什么是数据库权限的“覆盖原则”?我们还是从上面的例子说起。在上面的例子中,我们设置了一个安全规则: [代码]{ "read": "doc._openid == auth.openid", "write": "doc._openid == auth.openid" } [代码] 这个规则明确表示,只有用户的openid等于数据的_openid时,才有读写权限。 那么,当你去读取数据时,你的查询条件where中必须有 _openid: ‘{openid}’ 这个条件(新版本中系统不会自动帮你添加),否则就会抛出权限不足的错误。 同样的,当你使用update、delete等操作时,你的查询条件where中也必须有 _openid: ‘{openid}’ 。 下面我们给安全规则增加一点点复杂度,假设安全规则是这样的: [代码]{ "read": "doc._openid == auth.openid && doc.number > 10" "write": "..." } [代码] 也就是说,我们只允许读取number大于10的数据,那么我们可能会自然而然的认为 “如果我没有指明number是多少,系统会仅返回number大于10的数据,而number小于等于10的数据不会返回”。 实际情况是,如果你没有指明 “我要查询自己的数据,并且我要number要大于10(或大于11等)的数据”,那么系统会认为你没有权限读取数据,会直接抛出上面的错误,一条数据也不会返回。 因此你的查询语句必须这样写: [代码]db.collection('todo').where({ _openid: '{openid}', number: db.command.gt(10), // 或者 db.command.gt(11) 等 // 其他你想要的条件,但不可以有number小于等于10 }).get() [代码] 上面这两个条件缺一不可。 总结成一句话就是:你的查询条件必须覆盖安全规则,否则就抛错。 提醒:除了read和write,你还可以使用create、update、delete权限,见数据库规则编写。 区分开发环境与生产环境 假设你觉得当前插入一条数据的功能已经很酷了,你要发布出去给用户使用,然后你会发现一个问题:“你的开发环境和用户的正式环境写入的是同一个名称为todo的表”。 我们都是正经的程序员,绝对不会把开发环境的数据和生产环境的数据混在一起,那么我们应该怎么办呢? 上一篇文章中我提到,云开发并不支持在本地电脑上进行调试(这里指脱离云环境),因此你必须使用付费的云环境来开发和测试。 因此,有些开发者会购买两个云环境,一个用于开发和测试,另一个用于生产环境(提供给用户的正式环境,以后称生产环境),但我认为这种做法不适合个人或小企业(已有一定用户规模的除外)。首先,多一个开发环境意味着每年要多出240元左右的成本。其次,在开发环境和生产环境之间的数据同步和代码同步是一个很繁琐的事情。 因此,我个人主张使用同一个云环境进行开发和测试。为了避免误删数据,我们需要对数据库的接口进行一定的封装,让开发环境自动访问todo表,而生产环境自动访问p_todo表(这里的p_前缀表示production)。 注意:不要使用 “生产环境用todo表,开发环境用dev_todo表” 这种设计思路,因为这样很容易一不小心就修改了生产环境的数据。 下面请跟随我的步骤实现自动判断: 新建 utils/utils.js 文件: [代码]'use strict' const APP = getApp // 不要在这里执行getApp(),因为可能会返回undefined const utils = { /* === 运行环境 === */ running () { return this.globalData().running }, globalData () { return APP().globalData }, /* 判断是否是本地开发环境,当代码在微信开发者工具中运行时返回true,否则返回false */ isLocal () { return this.running().is_local }, isWindows () { return this.running().is_windows }, isMac () { return this.running().is_mac }, /* 判断是否是电脑,包含Windows和Mac 注意,本地开发环境会返回false */ isPC () { const r = this.running() return r.is_windows || r.is_mac }, // === 数据库 === /* 返回数据库访问对象 */ _db () { return APP().cloud.database() }, /* 请使用coll()代替默认的collection函数,以免误操作生产环境的数据 */ coll (c) { const _ = this return _._db().collection(_._collName(c)) // 此文件中只有这里可以写collection }, /* 生产环境下添加p_前缀(all_前缀的集合除外) */ _collName(c){ const _ = this if( !_.isLocal() && !c.startsWith('all_') ){ c = 'p_' + c } return c }, } export default utils [代码] 新建 utils/app_init.js 文件: [代码]'use strict' // 对app进行初始化,以后会在这里添加更多的初始化代码 import utils from 'utils' /* 初始化运行环境(很重要,决定了后续操作测试数据库还是正式数据库) */ const initRunning = (app) => { const platform = wx.getDeviceInfo().platform app.globalData.running ??= {} app.globalData.running.is_local = platform === 'devtools' app.globalData.running.is_windows = platform === 'windows' app.globalData.running.is_mac = platform === 'mac' } /* 这里需要显示传入app, 因为在app.js的App()注册函数执行完成之前, 调用getApp()可能会返回undefined */ export default function (app) { initRunning(app) } [代码] 编辑 app.js 文件,增加app的初始化调用,编辑后的代码大概如下: [代码]'use strict' import appInit from './utils/app_init' App({ onLaunch: function () { const _ = this // 我习惯使用_表示this // 这里是wx.cloud的初始化代码,请保留原样 appInit(_) // 在这里调用app的初始化函数 }, globalData: {}, // 请把globalData的定义移动到这里,onLaunch的外面 }); [代码] 如图所示,请保持相同的文件结构: [图片] 好了,现在当你使用如下代码时,就能自动区分开发环境和生产环境: [代码]utils.coll('todo').add({ data: { title: '学习云数据库', created: new Date(), } }) [代码] 如果是微信开发者工具中运行的(微信开发者工具就是开发环境,以后同),就会自动访问todo表,其他情况会自动访问p_todo表。 不过,你还得去云开发后台新建一个p_todo表,然后记得修改数据库权限和todo表一致。 [图片] 修改 pages/todo/todo.js 文件,把写入数据的代码改成如下: [代码]'use strict' import utils from '../../utils/utils' Page({ data: { }, onLoad(options) { }, async test1 (e) { utils.coll('todo').add({ data: { title: '学习云数据库', created: new Date(), } }) .then(res => { wx.showToast({ title: '新增记录成功', }) console.log(res) }) }, }) [代码] 然后在微信开发者工具中点击“写入第一条数据”按钮,你会发现todo表中多了一条数据。 我们再来测试一下在手机中运行,如图所示: [图片] 你会发现在手机上执行时,数据是写入到p_todo表中的。并且,当你发布小程序后,用户在使用时也会写入到p_todo表中。 注意:utils.js代码中有一句 “all_前缀的集合除外”,这是因为后期会有一些表是全局的,不区分开发环境和生产环境,这些表会以all_开头,我们将在后期讲解。 代码库 本系列教程搭配了一个github代码库:sdjl/WxMpCloudBooster,你可以在这里找到文章中的代码: [代码]# 获取项目 git clone https://github.com/sdjl/WxMpCloudBooster.git # 切换到文章一对应的代码库 cd WxMpCloudBooster git checkout article1 [代码] 我每发布一篇文章,就会提交一个commit,你可以使用 git checkout article + n 来切换到第n篇文章对应的代码。 下篇预告 在下一篇文章中,我们会进一步完善todolist的功能,并且会进一步讲解数据库的使用和限制,例如如何突破每次20条查询的限制,以及数据库查询每次到底可以查多少数据等。 我还会提供更丰富的代码库,utils.js工具会越来越丰富,你可以把utils.js导入到你自己的项目,实现高效率编码,还能降低成本,最终变成大神。 本文作者刘永辉,安顺果然赞科技有限公司,转载请注明出处。
07-26 - 小白变大神零:为什么你应该立即使用云开发写小程序?
《小白变大神,微信小程序云开发快速入门与成本控制实战》系列文章 第零篇:为什么你应该立即使用云开发写小程序? 本文主要内容 云开发给我们带来的新体验,如何通过把精力放在创造性的工作上,感受工作与生活的平衡。 介绍云开发的计费方式,了解云开发到底贵不贵,以及如何尽可能降低云开发的费用。 介绍了 “数据库的设计要优先服务于写代码的体验” 的理念。 一、前言 想象一下,如果编程不再需要处理繁琐的服务器问题,那将是多么幸福的事情。 自2009年毕业后,我就进入了互联网行业,我还记得当时的服务器是自己搭建的。与团队小伙伴自己购买大型服务器并置于北京市中心的机房,每次进入机房都要戴上鞋套,以免脚上的尘土洒落在机房里一层不染的地板砖上。 [图片] 从那时起,维护服务器就成为了一场噩梦,虽然现在已经有腾讯云等云服务商,但是服务器的远程维护依然让我感觉有一种“牵挂”。那种半夜被叫醒,服务器“挂”了,然后你要赶紧起床去处理的感觉,我再也不想经历。 所以,当我第一次接触到微信小程序云开发的时候,我感觉这份“牵挂”终于放下了。云开发是一个后端即服务(BaaS)的解决方案,它让你可以专注于小程序的前端开发,而不用再去操心所有和服务器相关的事情。 使用云开发,不仅是技术层面的转变,更是生活方式的改变。你可以将更多时间投入到创新创造中。这给我一种侠客游走江湖的感觉,当我合上电脑时,我就能从"江湖"中彻底回到现实世界,与家人、孩子充分享受生活,不再有"牵挂"。这种在工作与生活之间自由切换,享受新的平衡,让我觉得写代码就像拿着手柄打游戏一样,充满了乐趣。这是我推荐你使用云开发最重要的理由。 [图片] 通过这篇文章,我将带你了解云开发的核心优势,相信能让你感受到这种自由与高效的新生活体验。 二、云开发给我们带来的新体验 前面说到不需要维护服务器是一种享受,本节我们具体的讲讲云开发给我们带来的其他体验。 一键部署 所谓云开发,并不是完全不需要服务器,而是服务器由微信自己去维护与管理,我们只需要上传云函数代码即可。这里的云函数代码,其实就是一个Node.js项目,用户端小程序(前端)可以向微信云环境发起请求,微信云环境会负责调用你的云函数代码,然后返回结果给前端。 因此你唯一需要做的,就是编写小程序代码和云函数代码、然后在微信开发者工具中一键上传(小程序和云函数需分开操作),整个过程完全感觉不到有服务器的存在。 [图片] 前端直接读写数据库 通常,如果我们要向数据库中写入数据,需要先在后端编写接口,然后前端调用接口。而在云开发中,我们可以直接在前端读写数据库,根本不需要后端接口。这就感觉好比行走江湖多年,突然有一天,你发现根本不需要把剑从剑鞘中拔出来,一个剑气就能直接杀敌,瞬间完成数据库的读写操作。 [图片] 假如我们需要向商品表中插入一条数据,只需在前端这样写: [代码]// 获得数据库引用 const db = getApp().cloud.database() // 向products表中插入一条数据 db.collection('products').add({ data: { name: 'iPhone 15', price: 4999 } }) [代码] 而读取商品表中的数据,只需要这样写: [代码]const products = await db.collection('products').get() [代码] 你根本不需要知道数据库存储在哪里、用户端如何连接数据库、用户和数据库物理上的距离远不远、数据库是否备份、一秒钟有一万人访问能不能抗住,等等。这些问题,微信云环境都帮你很好的解决了。 [图片] (云开发就好像一个封装好的魔法水晶球,你只专注于代码,云环境会帮你解决其他问题。) 无需鉴权 发现没有,前面读取数据库的代码中,没有任何鉴权相关的代码。在云开发中,所有的鉴权都是由微信云环境自动完成的。包括数据库权限、用户登录以及_openid的获取、支付接口的调用、云函数调用、文件的读取、所有API的调用,等等,都不需要我们自己去写鉴权逻辑。 这种感觉,就好像有位你并不认识的高人走到你面前,主动帮你打通了任督二脉,你感觉全身通畅,刚刚把手指抬起来,六脉神剑就发出去了。 [图片] 由于所有的代码都运行在微信自己的服务器上,因此微信可以确保代码的安全性,我们只需要专注于业务逻辑的编写,不用担心自己的服务器被攻击,数据被盗取等问题。 前、后端都是JavaScript 在云开发中,前端和后端都是JavaScript(或TypeScript),这意味着你可以使用相同的语言编写前、后端代码。 云端return的数据,并不像传统服务器那样返回一个json字符串,而是直接返回一个对象,例如: [代码]// 实现加法计算的云函数代码 exports.main = async (event, context) => { const {x, y} = event return { sum: x + y, } } [代码] 而在前端代码中,可以直接这样调用: [代码]const result = await wx.cloud.callFunction({ name: 'add', // 这里add是云函数的函数名称 data: {x: 1, y: 2} }) console.log(result.sum) // 3 [代码] 看,前后端已经被打通了,前端传给后端的数据是js的对象,后端返回给前端的数据也是js的对象。 而且,由于前、后端都使用JavaScript,那么前、后端就可以共用一些工具函数,例如数据库的读取操作(但要注意前、后端对数据权限的处理与前端不一样),字符串和时间的处理,等等。 如果原本有一段代码是运行在前端的,突然有一天想把它移动到云端(为了防止别人阅读代码,或前端性能不足等原因),几乎不需要做太大的改动,只需要复制粘贴到云函数中即可。 所谓的后端只是一个运行在云端的云函数,站在你的角度,你只是在写一个函数,而不是在写后端,更不需要学习任何的框架,云函数return返回前端需要的数据即可。 从开发体验上来说,你根本不觉得自己在写后端。 三、云开发到底贵不贵? 前面已经介绍了云开发带来的新体验,但是作为开发者你可能听说过“云开发比传统服务器贵得多”的说法,那这种说法对不对呢?这个问题不能简单的回答“对”或者“不对”,因为这需要你进一步理解云开发的计费方式,然后根据自己的业务需求来判断。 总体而言,云开发是按照“基础套餐+按量计费”的方式收费的,这有点像手机话费套餐,通常来说你是用不完基础套餐资源的。如果某种资源的使用量超过了套餐限制,那你就需要额外后付费(而不是像传统服务器那样先购买配置)。 接下来我会根据自己的使用经验,重点介绍你可能关心的问题。 最低基础套餐费用 目前,云开发最便宜的基础套餐是19.9元/月,并且可以10个小程序共用一个套餐(小程序需同属于一人或公司)。 假设你有10个小程序,它们共用一个云开发环境,那么每个小程序的服务器成本可以低至2元/月。这种感觉有点像你进入一个客栈,店小二说“客官,我们这里吃一个饼是20元,吃十个也是20元,您要吃几个?”,不多吃几个都对不起自己。 当然,这里有一个前提,就是你的小程序用户量都不大,所有小程序消耗的资源总量不超过基础套餐的限制。那如果用户量大怎么办?我们后面会讲到。 “调用次数“产生的费用 就像你的手机套餐有通话分钟数、短信次数、流量等,云环境也有调用次数、容量、流量、云函数资源使用量、文件存储、CDN等。 其中需要重点介绍的是“调用次数”,它包含数据库的读写次数、云函数的调用次数、文件存储的读写次数。在每月19.9元的基础套餐中,每月有20万次的“调用次数”额度,超出的部分会按照0.5元/万次另收费。 我们来计算一下0.5元/万次的“调用次数”是什么概念,假设平均每个用户每日会消耗20次调用次数,你的小程序每天有500人访问,每日消耗就是1万次,也就是0.5元,这个费用,让其中一个用户看一个视频广告就能赚回来(注意,实际情况中除了调用次数的费用,还可能产生其他费用)。 在上面的代码中,向数据库中写入一条数据会消耗一次“调用次数”,读取数据时也会消耗一次“调用次数”。 [代码]// 获取数据库引用不会消耗“调用次数” const db = getApp().cloud.database() // 下面的写入操作会消耗一次“调用次数” db.collection('products').add({ data: { name: 'iPhone 15', price: 4999 } }) // 下面的读取操作也会消耗一次“调用次数” const products = await db.collection('products').get() [代码] 另外,每次使用callFunction调用云函数时也会消耗一次“调用次数”。 如果小程序在“调用次数”上超过了基础套餐的限制,那很可能是你对数据库的读写过于频繁(或者说数据库设计不合理)。 当我们使用云数据库的这种计费方式时,我们必须在编码习惯上有所转变,在设计数据库和编码时就要考虑到对“调用次数”的影响。 举个例子,假设用户首次进入小程序时会从数据库读取小程序配置信息,例如首页的banner图、字体大小、导航栏的文案、提示弹窗文案等。通常你会把这些配置都放在同一张表中,但你有可能不会一次性读取所有配置数据,而是在使用到A数据时就读一下A数据,使用到B数据时就读一下B数据,并且下一次使用到相同的数据时,又会再读取一次。如果这样写代码,就会成倍的增加“调用次数”的消耗,那所谓的“云开发比传统服务器贵得多”就很可能发生。 你肯定立刻能想到,可以使用缓存来减少调用次数。这确实是行得通的,以后有机会我会专门用一篇文章把我的经验分享给你。 按量付费与预付费 作为程序员,我们按年支付购买的传统服务器,通常99%的CPU时间都是闲置的。因此,当你在对比云开发和传统服务器的费用时,你不能假设你可以100%利用传统服务器的资源(10%都不行),然后根据这个资源利用率来计算云开发的费用,这种对比方式没有意义。 如果你的产品用户量不大,那么云开发的费用并不高(每年约240元)。但如果用户量大呢?用户量大你怕啥,你的产品肯定赚钱了呀!此时云开发的成本就不是问题了。(当然也有用户量大但就是不挣钱的产品,这不是应该用哪种服务器的问题,而是应该放弃的问题。) 如果你的产品有一定的用户量,那么节约这点云开发的成本并不是你应该优先考虑的。一个产品的稳定性才是更重要的,没有人愿意使用一个不稳定的产品,为了节约一点点成本,而牺牲产品的稳定性,最后导致自己错过好不容易偶遇的机会,那是远远不值得的。 [图片] 并且,就算你今日有一万个用户,不代表三个月后也会有这么多用户,未来是不可预测的,提前规划购买了高配置的服务器所带来的损失,可能比云开发的成本费用还要高。 我需要为开发或测试单独购买云环境吗? 云开发并不支持在本地电脑上进行调试(这里指脱离云环境),因此你必须有一个付费的云环境来进行开发和测试。因此,有些开发者会购买两个云环境,一个用于开发和测试,另一个用于生产环境,但我认为这种做法不适合个人或小企业(已有一定用户规模的除外)。 首先,多一个开发环境意味着每年要多出240元左右的成本。其次,在开发环境和生产环境之间的数据同步和代码同步是一个很繁琐的事情,一会切换到开发环境,一会切换到生产环境,这会让写代码这件原本很幸福的事变得很劳累。 因此,我个人主张使用同一个云环境进行开发和测试。为了避免误删数据,我们需要对数据库的接口进行一定的封装,让开发环境自动访问xxx表,而生产环境自动访问p_xxx表(这里的p_前缀表示production)。由于这个问题展开后篇幅较多,我会在以后的文章中详细介绍。 如何尽可能降低云开发的费用? 如果你不改变编码习惯,那云开发的费用可能会成指数级增长。 这里介绍10个常用的降低云开发费用的编码习惯: 合理设计数据库结构,减少读写次数。例如,当显示一个商品详情页时,要尽可能仅读取一次数据库,这就要求设计数据库时,把商品的基本信息、商品图片、用户评论等都放在同一个数据文档中。 尽可能一次性读取较多数据。例如,当显示一个列表页时,尽可能一次性读取1000条数据,然后分多次显示给用户。(云开发API有单次只能读取20条的限制,后期我会介绍如何超过这个限制。) 使用本地缓存。例如,当用户首次进入商品列表页时,将商品列表数据缓存到本地,这样用户从商品详情页回到列表页时,就不需要再次读取数据库。甚至,可以在读取列表数据时,把详情页数据也一并读取,这样用户进入详情页时,就不需要再次读取数据库。(单次读取数据的总大小有限制,以后详细介绍。) 使用webp格式的图片。webp格式的图片比png、jpg小很多,这样可以减少网络流量的成本。 降低云函数的内存配置。云函数的费用是按照“内存配置x运行时间”计算的,内存配置越高,费用越高。目前默认是256M,你最低可以手动降到128M,这样就能降低一半的费用(通常你连128M也用不到)。 在云函数中,尽可能用Promise.all并发执行,以减少云函数的运行时间。例如,你有10个文件要下载,你可以使用Promise.all并发下载。 文件尽可能放在“静态存储”中,而不是“动态存储”中,后者的存储费用是前者的20倍。 宁可存储冗余数据,也要减少数据库的“调用次数”。例如,一个商品的用户评论,可以把用户的头像、昵称和评论内容都存储在商品文档中,而不是存一个用户id,然后再用id去用户表中读取用户的头像、昵称。什么,你说万一用户修改了昵称或头像怎么办?请看下一个建议。 能不update的就不要update。毕竟用户修改头像和昵称并不影响其他人阅读商品评论,如果用户修改了头像和昵称,那么他在商品评论中的头像和昵称不更新也没关系。这不仅仅是为了减少“调用次数”,更重要的是能不写的代码就不要写。 避免重复计算。例如,有一个云函数会消耗10次“调用次数”,但不同的用户调用这个云函数时,返回的结果是一样的,那么可以把结果缓存到数据库中,下次直接读取函数缓存结果。 相信如果你能做到以上10点,那么云开发的费用不会很高。 四、云开发是一种享受编程的体验 如果说你已经感受到使用云开发不需要和服务器打交道的幸福体验,并且也不再担心云开发的费用,但你依然犹豫不决,那我相信一定是你担心从MySQL等关系型数据库迁移到文档型数据库会让你不习惯。 先说我个人的感受,在前、后端直接操作数据库给我带来的便利性,远远超过了那种不想改变编码习惯的排斥感。 如今,我们还有Copilot、ChatGPT等AI工具,当你想要插入或更新一条数据时,Copilot会一秒钟帮你写完数据库的API调用代码,而复杂的查询语句可以让ChatGPT或Claude帮你写,这大大的降低了我们学习文档型数据库的时间成本。 接下来,我们通过一个案例,来看看小程序云数据库的优势。 给商品添加评论 假设我们有一个商品表products,并且商品的评论和其他信息是存储在同一个文档中的,如: [代码]{ "_id": "id_1", "name": "iPhone 15", "price": 4999, "comments": [ { "content": "good", "time": "2022-01-01" }, { "content": "bad", "time": "2022-01-02" } ] } [代码] 那么,当我们要给商品添加一条评论时,只需要这样写: [代码]// 获得数据库引用 const db = getApp().cloud.database() const _ = db.command db.collection('products').doc('id_1').update({ data: { comments: _.push([{ content: 'very good', time: new Date() }]) } }) [代码] 关键是,上面这段代码,可以直接写在前端,不需要后端接口(云函数),当然你也可以很方便的复制到云函数中。 允许冗余数据 与使用MySQL关系型数据库的习惯不同,当我使用文档型数据库时,我会经常存储冗余数据。 这样做不仅仅是为了减少数据库的“调用次数”,更重要的是让写代码的体验变得更好。 举个例子,如果要删除一个商品以及这个商品的评论,如果使用关系型数据库,你至少要写两个SQL语句,一个删除商品,另一个删除商品的评论(如果你还有商品图片表、商品类型表、搜索关键词表、相关商品推荐表等,那就需要更多删除语句)。而在文档型数据库中,你只需要删除一个文档即可,如: [代码]db.collection('products').doc('id_1').remove() [代码] 我个人认为,牺牲一点存储空间,换取写代码的体验,让产品尽快上线,是非常值得的。 数据库的设计要优先服务于写代码的体验 说句扎心的话,你写的程序很可能没人用。既然是这样,那就在写代码这件事情上,尽可能少花时间,用最低的时间成本去验证产品的可行性,这才是最重要的。(当然,如果你在腾讯、Google这样的公司,那这个建议对你来说是不适用的。) 不要想着你的产品将来可能会有很多人用,如果真的有很多人用,你再花时间去重构代码也不迟,我相信到时候你不会嫌弃这点工作量的。 天下武功,唯快不破,在从0-1创造一个产品验证想法上,也是如此,云开发就是我们的凌波微步和无影手。 因此,数据库的设计要优先服务于写代码的体验,一句代码就能完成的不要写多句,能不update的就不要update,能不写的代码就不写,产品上线速度应该是你最关心的问题。 结语 [图片] (快速迭代,验证想法) 用最少的开发时间、最舒适的编码体验,完成产品最可能被用户需要的基础功能。去验证你的想法是否满足真实的用户需求,发布后立即进入到下一个产品的研发,不用管服务器端的琐事,把精力放在创造性的工作上,享受创造的乐趣,感受幸福的生活,这就是你应该立即使用云开发的理由。 继续阅读 点击下一篇文章开始我们的学习之旅:小白变大神,微信小程序云开发快速入门与成本控制实战:一、初识云开发数据库 (本文作者刘永辉,安顺果然赞科技有限公司,转载请注明出处。文章图片由作者写作时使用DALL-E-3生成,无须担心图片侵权问题)
07-26