顾名思义,就是把所有逻辑放在一个云函数中。
优缺点
这种方式有很多优点:
- 多项目可以放在一个实例中,互不影响,又可以相互访问。
- 多项目可以放在一个实例中,共享资源,降低成本。
- 方便迁移,一个项目就只有1个(或几个)文件,一个复制粘贴就可以搞定。
同样,也会有一些缺点:
- 如果没有良好的结构和约定,最终真的会堆出屎山。
- 一旦有一个方法崩了,可能会影响整个项目的健康状态。
- 安全性降低,如果有前端直接操作数据库,那访问策略一定要搞好。
另外,还有一些不确定的内容:
- 实例的内存占用是否会增加。毕竟随便一个请求,都会加载整个函数。
约定
为了防止屎山不那么快的出现,做以下约定:
基于 GET 中的 query.action 参数区分请求。所以这是个必传参数。
根据实际需求选择 GET 或 POST 或其它请求方式,同时传递所需参数。
制定一套合理的状态码。我这里是在 http 状态码的基础上增加两位方便扩充,如200=>20000。
约定一个功能函数的 interface(初次尝试ts,若有问题请指正):
interface Action {
[key: string]: {
methods: ('GET' | 'POST')[]
auth: boolean
handler: Function
}
}
实现
给项目起个代号firefly
然后创建它,把前面的 interface 放进去。
import cloud from '@lafjs/cloud'
interface Action {
[key: string]: {
methods: ('GET' | 'POST')[]
auth: boolean
handler: Function
}
}
const actions: Action = {}
export default async function (ctx: FunctionContext) {
console.log('Hello World')
return { data: 'hi, laf' }
}
补一下前置处理,也就是拦截器。
...
export default async function (ctx: FunctionContext) {
const { action } = ctx.query
if (!action || !actions[action]) {
return {code: 40000}
}
const { methods, auth, handler } = actions[action]
if (!methods.includes(ctx.method)) {
return {code: 40000}
}
if (auth && ctx.user === null) {
return {code: 40100}
}
if (handler) {
return handler(ctx)
}
return {code: 50000}
}
然后定义第一个处理函数 GET https://appId.laf.run/firefly?action=test
...
actions.test = {
methods: ['GET'],
auth: false,
handler() {
return {
hello: 'world'
}
}
}
...
至此,单文件函数的框架就搭建好了。然后,补一下配置项,和几个常用方法。
const config = {
name: 'firefly',
collectionPrefix: 'firefly',
sharedPrefix: 'firefly'
}
const collection = (name) => {
return cloud.database().collection(`${config.collectionPrefix}_${name}`)
}
const shared = {
get(key) {
return cloud.shared.get(`${config.sharedPrefix || ''}_${key}`)
},
set(key, value) {
return cloud.shared.set(`${config.sharedPrefix || ''}_${key}`, value)
}
}
再尝试写个处理函数 POST https://appId.laf.run/firefly?action=user&id=1
...
actions.user = {
method: ['POST'],
auth: true,
async handler(ctx){
const { id } = ctx.query
if(!id){
return {code: 40000}
}
const userInfo = await collection('user').doc(id).get()
if(!userInfo.ok || userInfo.data === null){
return {code: 50000}
}
return {
code: 20000,
message: '',
data: userInfo.data
}
}
}
...
完整代码
import cloud from '@lafjs/cloud'
interface Action {
[key: string]: {
methods: ('GET' | 'POST')[]
auth: boolean
handler: Function
}
}
const config = {
name: 'firefly',
collectionPrefix: 'firefly',
sharedPrefix: 'firefly'
}
const collection = (name) => {
return cloud.database().collection(`${config.collectionPrefix}_${name}`)
}
const shared = {
get(key) {
return cloud.shared.get(`${config.sharedPrefix || ''}_${key}`)
},
set(key, value) {
return cloud.shared.set(`${config.sharedPrefix || ''}_${key}`, value)
}
}
const actions: Action = {}
actions.test = {
methods: ['GET'],
auth: false,
handler() {
return {
hello: 'world'
}
}
}
actions.user = {
method: ['POST'],
auth: true,
async handler(ctx){
const { id } = ctx.query
if(!id){
return {code: 40000}
}
const userInfo = await collection('user').doc(id).get()
if(!userInfo.ok || userInfo.data === null){
return {code: 50000}
}
return {
code: 20000,
message: '',
data: userInfo.data
}
}
}
export default async function (ctx: FunctionContext) {
const { action } = ctx.query
if (!action || !actions[action]) {
return {code: 40000}
}
const { methods, auth, handler } = actions[action]
if (!methods.includes(ctx.method)) {
return {code: 40000}
}
if (auth && ctx.user === null) {
return {code: 40100}
}
if (handler) {
return handler(ctx)
}
return {code: 50000}
}
最后
简单介绍一下实现这个单文件云函数的项目:
一个给孩子创造压力的小工具,也是解放宝爸宝妈的小工具。一键生成各种数学题,字帖。
地址在这里:https://www.dayin.page/
它本来是一个纯前端工具。借助 Laf 的能力,现在接入了微信小程序,扫一扫即可查看答案。
感谢 Laf,希望单文件云函数能给你带来启发,也希望你喜欢 dayin.page 。
—
原文:https://www.muyi.dev/posts/20230622
update:20230706
已发布至云函数市场:https://laf.run/market/templates/649ccff79622c81462953d74