diff --git a/docs/doc-images/edit-cloudfunction.png b/docs/doc-images/edit-cloudfunction.png index 12a5238040..8c1a522a79 100644 Binary files a/docs/doc-images/edit-cloudfunction.png and b/docs/doc-images/edit-cloudfunction.png differ diff --git a/docs/doc-images/function-body.png b/docs/doc-images/function-body.png new file mode 100644 index 0000000000..1f29b6d132 Binary files /dev/null and b/docs/doc-images/function-body.png differ diff --git a/docs/doc-images/function-query.png b/docs/doc-images/function-query.png new file mode 100644 index 0000000000..77a46d308d Binary files /dev/null and b/docs/doc-images/function-query.png differ diff --git a/docs/doc-images/getToken-parseToken.png b/docs/doc-images/getToken-parseToken.png new file mode 100644 index 0000000000..1e93ed6188 Binary files /dev/null and b/docs/doc-images/getToken-parseToken.png differ diff --git a/docs/guide/function/call-function-in-client.md b/docs/guide/function/call-function-in-client.md index 953d77b764..2addeeee87 100644 --- a/docs/guide/function/call-function-in-client.md +++ b/docs/guide/function/call-function-in-client.md @@ -4,7 +4,7 @@ title: 在客户端中调用 # {{ $frontmatter.title }} -云函数编写完成并发布后,客户端可通过 `sdk` 的方式进行调用。 +云函数编写完成并发布后,客户端可通过 `laf-client-sdk` 进行调用。 ::: info 目前 SDK 暂时只支持发送 POST 请求 @@ -16,7 +16,8 @@ title: 在客户端中调用 npm i laf-client-sdk ``` -## 初始化 `cloud` 对象: +## 初始化 `cloud` 对象 + ```typescript import { Cloud } from "laf-client-sdk"; @@ -37,4 +38,6 @@ console.log(res) // 这里的 res 是云函数中 return 的内容 怎么样,是不是很方便, 只需简单的配置和一行代码即可实现对云函数的调用。 +## laf-client-sdk 详细文档 +查看详细文档:[client-sdk](/guide/client-sdk/) diff --git a/docs/guide/function/call-function-in-http.md b/docs/guide/function/call-function-in-http.md index 62080061ef..21eee1065b 100644 --- a/docs/guide/function/call-function-in-http.md +++ b/docs/guide/function/call-function-in-http.md @@ -1,16 +1,16 @@ -# HTTP 调用 +# HTTP 调用 云函数每个云函数都提供了 API 地址,理论上我们可以在任何能发起 http 请求的地方调用云函数。 -![](../../doc-images/function-url.png) +![function-url](../../doc-images/function-url.png) -下面是用 axios 请求云函数的简单示例。 +下面是用前端使用 `axios` 请求云函数的简单示例。 ```ts - const { data } = await axios({ - url: "", - method: "get", - }); +const { data } = await axios({ + url: "", + method: "get", +}); - console.log(data); -``` \ No newline at end of file +console.log(data); +``` diff --git a/docs/guide/function/call-function.md b/docs/guide/function/call-function.md index 55d285452d..4313bbbcc6 100644 --- a/docs/guide/function/call-function.md +++ b/docs/guide/function/call-function.md @@ -6,7 +6,6 @@ title: 在云函数中调用 云函数在开发完毕并发布后,可以在其他云函数中进行调用。 - ## 编写并发布待调用云函数 比如,我们创建一个名为 `get-user-info` 的云函数,并编写如下代码: @@ -22,32 +21,29 @@ export async function main(ctx: FunctionContext) { if (!userid) return { err: 1, errmsg: 'userid not exists' } const userCollection = db.collection('user') - const { data: user } = await userCollection.where({ id: userid }).get() if (!user) return { err: 2, errmsg: 'user not found' } - return { err: 0, data: user } } ``` 该函数接收一个名为 userid 的参数, 并通过id在数据库中查找相应用户,并将 查找到的数据返回。 - ## 调用已发布云函数 +::: info +`cloud.invoke`方法已不推荐使用,可使用[引入云函数](/guide/function/use-function.html#云函数引入) +::: + `get-user-info` 云函数发布后, 我们可以在其他云函数调用该函数。 ```typescript import cloud from '@lafjs/cloud' export async function main(ctx: FunctionContext) { - - const res = await cloud.invoke('get-user-info', { ...ctx, body: { userid: 'user id' }}) - console.log(res) - } ``` @@ -55,4 +51,4 @@ export async function main(ctx: FunctionContext) { 该方法接收两个参数:第一个参数为 待调用的函数名,第二个参数为传递的数据 -可以看到,`body` 传入了我们请求参数,如果函数内部需要使用ctx中的某些属性,还可用 `...ctx` 的方式传入ctx以便被调用函数使用。 \ No newline at end of file +可以看到,`body` 传入了我们请求参数,如果函数内部需要使用ctx中的某些属性,还可用 `...ctx` 的方式传入ctx以便被调用函数使用。 diff --git a/docs/guide/function/depend.md b/docs/guide/function/depend.md index 8004aca130..6ad72a5b10 100644 --- a/docs/guide/function/depend.md +++ b/docs/guide/function/depend.md @@ -16,7 +16,6 @@ title: 依赖管理 ![](/doc-images/package-list.png) - 安装完成后用户可在界面左下方 `依赖管理` 中查看已安装的依赖和版本。 ## 版本 @@ -27,10 +26,9 @@ title: 依赖管理 添加依赖时,默认勾选为最新版`latest`,用户如需指定版本,可在安装时在版本下拉框中选择对应版本。 - ## 云函数使用 -安装完成后,即可在云函数中引入并使用。例如,创建一个云函数 `hello-moment`,并修改代码如下: +安装完成后,即可在云函数中引入并使用。例如,创建一个云函数 `hello-moment`,并修改代码如下: ```typescript import cloud from '@lafjs/cloud' @@ -53,4 +51,4 @@ export async function main(ctx: FunctionContext) { "now": "2023-02-08 02:14:05", "twoHoursLater": "2023-02-08 04:14:05" } -``` \ No newline at end of file +``` diff --git a/docs/guide/function/index.md b/docs/guide/function/index.md index 2791ca0d5b..ef5f82c21f 100644 --- a/docs/guide/function/index.md +++ b/docs/guide/function/index.md @@ -4,36 +4,54 @@ title: 云函数入门 # {{ $frontmatter.title }} -云函数是运行在云端的 JavaScript 代码。 +## 云函数简介 -云函数可使用 Typescript 编写,无需管理服务器,在开发控制台在线编写、在线调试、一键保存即可运行后端代码。 +`Laf云函数`是运行在云端的 `JavaScript` 代码。 -在你的应用中,大多数数据的获取都可在客户端直接操作数据库,但是通常业务中会使用到「非数据库操作」,如注册、登录、文件操作、事务、第三方接口等,可直接使用云函数实现。 +云函数可使用 `Typescript/JavaScript` 编写,无需管理服务器,在Web开发控制台在线编写、在线调试、一键保存即可运行后端代码。 + +可在无需购买和管理服务器的情况下,快速开发后端代码。并且自带数据库和对象存储,极大降低后端开发难度。 + +每个云函数都是一个单独的 `Typescript` 文件,Laf为云函数单独封装了 `@lafjs/cloud` 模块,以便于更加方便的编写云函数。 ## 创建云函数 -点击页面左上角「函数」按钮,点击加号,添加云函数 +创建并进入Laf应用后,点击页面左上角「函数」按钮,点击加号,添加云函数 + +![create-function](/doc-images/create-function.png) + +**函数名** : 一般以字母或_开头,可使用`字母 数字 - _`四种,函数名不可重复 + +**标签** : 用来分类管理的,可通过标签名筛选云函数,可为每个云函数设置多个标签 -![](/doc-images/create-function.png) +**请求方式** : 只有被勾选的请求方式才允许请求 + +**函数描述** : 方便后续查看云函数的功能,相当于备注 + +**函数模板** : 选择不同的函数模板可初始化不同的代码 ## 编辑云函数 -可直接在线编辑代码 +Laf自带 `Web IDE`,可直接在浏览器在线编辑、运行(调试)、发布云函数 -![](/doc-images/edit-cloudfunction.png) +![edit-cloudfunction](/doc-images/edit-cloudfunction.png) ## 运行云函数 -云函数可直接运行调试,未发布的云函数也可以在此进行运行调试 +云函数编写后可直接运行调试,未发布的云函数也可以在此进行运行调试 + +![run-cloudfunction](/doc-images/run-cloudfunction.png) + +如在云函数中添加 `console.log` 打印日志的代码,运行后也会直接显示在Console控制台,同时也会将云函数的返回值打印在运行结果中。 -![](/doc-images/run-cloudfunction.png) +可切换请求方式以及配置请求参数,默认是 `GET` 请求,此处显示的请求方式与勾选的请求方式有关。 ## 发布云函数 -云函数发布后,才可正式使用 +云函数发布后,才是正式生效。前端才可以进行请求。 ::: warning -云函数不会自动保存,发布后才会保存并生效 +**云函数修改的代码,会自动在当前浏览器中缓存,只有在发布后才是真正的保存以及生效!** ::: -![](/doc-images/publish-cloudfunction.png) \ No newline at end of file +![publish-cloudfunction](/doc-images/publish-cloudfunction.png) diff --git a/docs/guide/function/use-function.md b/docs/guide/function/use-function.md index 7dbe05c1f2..aedab8004d 100644 --- a/docs/guide/function/use-function.md +++ b/docs/guide/function/use-function.md @@ -1,14 +1,17 @@ --- -title: 云函数参数 返回值 +title: 云函数用法 --- # {{ $frontmatter.title }} +## 云函数参数 -## 参数 +在 `main` 函数中,可以通过参数 `ctx` 来获取用户传递的请求信息。 +下面的例子可以读取前端传递的 `Query` 参数`username`: -在 `main` 函数中,可以通过第一个参数 `ctx` 来获取用户传递的请求信息。 -下面的例子可以读取前端传递的 Query 参数`username`: +![function-query](/doc-images/function-query.png) + +云函数代码如下: ```js export function main(ctx: FunctionContext) { @@ -16,14 +19,19 @@ export function main(ctx: FunctionContext) { }; ``` -这样可以读取前端传递的 body 参数 +还可以读取前端HTTP请求传递的 `body` 参数`username`: + +![function-query](/doc-images/function-body.png) + +云函数代码如下: + ```js export function main(ctx: FunctionContext) { - console.log(ctx.body) + console.log(ctx.body.username) }; ``` -`ctx` 具有下面的一些内容: +`ctx` 具有下面的一些参数: | 属性 | 介绍 | | --------------- | ----------------------------------------------------------------------------------- | @@ -33,14 +41,20 @@ export function main(ctx: FunctionContext) { | `ctx.user` | 使用 Http Bearer Token 认证时,解析出的 token 值 | | `ctx.query` | 当前请求的 query 参数 | | `ctx.body` | 当前请求的 body 参数 | +| `ctx.request` | HTTP 响应,和`express`的`Request`实例保持一致 | | `ctx.response` | HTTP 响应,和`express`的`Response`实例保持一致 | -| `ctx.socket` | [WebSocket](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket) 实例 | +| `ctx.socket` | [WebSocket](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket) 实例,[Laf WebSocket使用文档](/guide/function/websocket.html) | | `ctx.files` | 上传的文件 ([File](https://developer.mozilla.org/zh-CN/docs/Web/API/File) 对象数组) | -| `ctx.env` | 自定义的环境变量 ([env](env.md)) | +| `ctx.env` | 本应用自定义的环境变量 ([env](env.md)) | + +## 云函数返回值 + +那我们如何把数据返回出去呢? -## 返回值 +### 方法1:return + +很简单,只需要在云函数中 return 出去就可以了 -那我们如何把数据传给前端呢?很简单,只需要在云函数中 return 出去就可以了。 ```js export function main (ctx: FunctionContext) { // 这里用字符串示例,你可以返回任何数据类型。 @@ -51,55 +65,142 @@ export function main (ctx: FunctionContext) { 云函数的返回值支持多种类型: ```js -Buffer.from("whoop"); // Buffer -{ +return Buffer.from("whoop"); // Buffer +return { some: "json"; } // 对象,会被处理成JSON -("

some html

"); // HTML -("Sorry, we cannot find that!"); // 字符串 +return ("

some html

"); // HTML +return ("Sorry, we cannot find that!"); // 字符串 +``` + +### 方法2: ctx.response设置响应头、状态码和响应体等信息 + +这里`ctx.response`对齐`express`框架的`Response`实例 + +以下是一些常见的 res 对象方法: + +```js +ctx.response.send(body) // 发送响应体,可以是一个字符串、一个Buffer对象、一个JSON对象、一个数组等 +ctx.response.json(body) // 发送一个JSON响应 +ctx.response.status(statusCode) // 设置HTTP响应的状态码 +ctx.response.setHeader(name, value) // 设置一个响应头 +... ``` -如果需要发送状态码,则需要使用 `ctx` 对象上的 `response` 属性: +如果需要发送状态码,则需要使用 `ctx.response.status` : ```js ctx.response.status(403); // 发送403状态码 ``` -## 异步的云函数 +如果需要分段发送数据,则需要使用 `ctx.response.write` 和 `ctx.response.end` : + +例如: + +```js +export function main (ctx: FunctionContext) { + // 设置响应头 + ctx.response.type = 'text/html'; + ctx.response.status = 200; + // 写入数据块 + ctx.response.write(''); + ctx.response.write('

Hello, world!

'); + ctx.response.write(''); + // 结束响应 + ctx.response.end(); +}; +``` + +## 支持异步操作 + +在实际应用中,云函数需要执行的异步操作(如网络请求,数据库操作等)。 + +幸运的是,云函数本身是支持异步调用的,你只需要在函数的前面加上 `async` ,就能轻松的让函数支持异步操作 + +新建云函数时默认已经在 main 函数的前面加上 `async` + +::: info +在云函数中执行异步操作,尽可能的使用 `await` 去等待执行完成 +::: + +如下面的例子,去查询数据库中的user集合 + +```js +import cloud from '@lafjs/cloud' +const db = cloud.database() + +exports.main = async function (ctx: FunctionContext) { + // 在数据库等异步操作前面添加 await + const res = await db.collection('user').get() + // 同步操作无需添加 await + console.log(res.data) +}; +``` + +## 云函数引入 + +现可直接在云函数中引入另外一个云函数 + +引入写法: -在实际应用中,云函数需要执行的异步操作(如网络请求)。 +```js +// funcName 为default函数 +import funcName from '@/funcName' +// 引入名为func的函数 +import { func } from '@/funcName' +``` -幸运的是,云函数本身是支持异步调用的,你只需要在函数的前面加上 `async` ,就能轻松的让函数支持异步操作: +如:在`test`云函数中引入`util`云函数 ```js -export async function main (ctx: FunctionContext) (ctx) { - await someAsyncAction; - return `hello, ${ctx.query.username}`; +// util 云函数 +export default async function main () { + return "util 已引入" +}; + +export function add(a: number, b: number) { + return a + b }; ``` -## Cloud SDK +```js +// test 云函数 +import util, { add } from '@/util' -刚刚编写的一些云函数都是比较基础的一些功能,并没有和 Laf 的其他功能连接起来。 +export async function main(ctx: FunctionContext) { + // 由于 util的default方法是async的,所以需要加await + console.log(await util()) + // 打印结果:"util 已引入" + console.log(add(1, 2)) + // 打印结果:3 +} +``` -在云函数上,Laf 提供了云 SDK `@lafjs/cloud` 让云函数支持访问网络、数据库、对象存储等。 +## Laf 云函数 Cloud SDK -::: warning +上面查询数据库的部分有引入Laf的Cloud SDK + +在云函数上,Laf 提供了专门的SDK `@lafjs/cloud` 让云函数支持访问网络、数据库、对象存储等。 + +::: danger `@lafjs/cloud` 是一个专有的模块,只能在云函数上使用,不支持通过 npm 安装到其他位置。 ::: ### 导入 SDK -SDK 的所有内容通过它的默认导出来访问。 +每个Laf应用默认已经安装了SDK依赖,不需要额外安装了。直接在云函数顶部import即可。 ```js import cloud from "@lafjs/cloud"; ``` + ### 发送网络请求 使用 `cloud.fetch()` 可发起 HTTP 请求,调用三方接口,可完成如支付接口、短信验证码等等三方接口操作。 -该接口是对 `axios` 请求库的封装,其调用方法与 `axios` 完全一致。 +该接口是对 `axios` 请求库的封装,其调用方法与 `axios` 完全一致。调用方法可参考:[axios文档](https://www.axios-http.cn/docs/api_intro) + +可以理解为 `cloud.fetch === axios`,可以做到互相替换。 ```ts import cloud from "@lafjs/cloud"; @@ -109,7 +210,6 @@ export async function main(ctx: FunctionContext) { url: "http://api.github.com/", method: "post", }); - console.log(ret.data); return ret.data; }; @@ -143,6 +243,10 @@ export async function main(ctx: FunctionContext) { 通过`cloud.invoke()` 调用本应用内的其他云函数。 +::: info +该方法已不推荐使用,现可直接[引入云函数](#云函数引入) +::: + ```ts import cloud from '@lafjs/cloud' @@ -151,7 +255,9 @@ export async function main(ctx: FunctionContext) { await cloud.invoke('hello') } ``` + 如果调用的云函数需要用到 ctx 里面的东西,我们可以通过这样的方式传入。 + ```ts import cloud from '@lafjs/cloud' @@ -161,11 +267,40 @@ export async function main(ctx: FunctionContext) { } ``` -### 生成 JWT token +### 生成和解密 JWT token + +```ts +cloud.getToken(payload); // payload可参考下方的示例代码 +cloud.parseToken(token); // token为前端请求时header里的authorization中的token +``` -以下实现简单登录函数,以演示 标准 JWT token 的生成,预期开发者已熟悉 JWT 相关知识。 +以下实现简单的生成和解密JWT token -可查看[JSON Web Token 入门教程](https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html) +```js +import cloud from "@lafjs/cloud"; + +export async function main(ctx: FunctionContext) { + const payload = { + uid: 1, + // 默认 token 有效期为 7 天,请务必提供此 `exp` 字段,详见 JWT 文档。 + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, + }; + // 生成access_token + const access_token = cloud.getToken(payload); + console.log("云函数生成的token:", access_token) + // ctx.user 会自动解密 + console.log(ctx.user) + const authHeader = ctx.headers.authorization; + const token = authHeader.split(' ')[1]; // 提取 JWT + console.log("前端请求带的token:", token) + const parseToken = cloud.parseToken(token); + console.log("解密token后的数据:", parseToken) +}; +``` + +![getToken-parseToken](/doc-images/getToken-parseToken.png) + +以下实现简单登录函数,并生成JWT token > 注意:出于演示目的,对 password 以明文方式查询,并未做 hash 处理考虑,不建议实际开发过程中如此使用。 @@ -202,10 +337,10 @@ export async function main(ctx: FunctionContext) { }; ``` -### 操作缓存数据 +### 云函数全局缓存 -::: info 云函数全局内存单例对象,可跨多次调用、不同云函数之间共享数据 + `cloud.shared`是 JS 中标准的 Map 对象,可参照 MDN 文档学习使用:[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) 使用场景: @@ -213,7 +348,10 @@ export async function main(ctx: FunctionContext) { 1. 可将一些全局配置初始化到 shared 中,如微信开发信息、短信发送配置 2. 可共享一些常用方法,如 checkPermission 等,以提升云函数性能 3. 可做热数据的缓存。如:缓存微信 access_token。(建议少量使用,此对象是在 node vm 堆中分配,因为 node vm 堆内存限制) - ::: + +::: info +应用重启后缓存会全部清空,不重启会一直保留 +::: ```ts import cloud from "@lafjs/cloud"; @@ -226,4 +364,23 @@ export async function main(ctx: FunctionContext) { await cloud.shared.clear(); // 清空所有缓存 // ... 其他方法可访问上方MDN的Map文档查看 }; -``` \ No newline at end of file +``` + +### 云函数原生MongoDriverObject实例 + +可参照 mongodb 官方crud文档学习使用:[mongodb](https://www.mongodb.com/docs/mongodb-shell/crud/) + +下面是一个简单的使用实例: + +```ts +import cloud from "@lafjs/cloud"; + +export async function main(ctx: FunctionContext) { + const db = cloud.mongo.db + const data = { + name : "张三" + } + const res = await db.collection('test').insertOne(data) + console.log(res) +}; +``` diff --git a/docs/guide/function/websocket.md b/docs/guide/function/websocket.md index 8907c2c4ad..e576f37c7a 100644 --- a/docs/guide/function/websocket.md +++ b/docs/guide/function/websocket.md @@ -4,16 +4,21 @@ title: 云函数处理 WebSocket 长连接 # {{ $frontmatter.title }} -## 特殊函数名 __websocket__ -如果需要使用 WebSocket 需要创建一个云函数并且命名为 `__websocket__`,这个云函数并不会运行之后销毁,会一直存在,专为 WebSocket 存在的云函数! +## 特殊函数名 __websocket__ + +如果需要使用 WebSocket 需要创建一个云函数并且命名为 `__websocket__`,这个云函数并不会运行之后销毁,会一直存在,专为 WebSocket 存在的云函数! + +::: info +`__websocket__` 为固定名称,仅有此云函数不会被销毁,会一直存在,专为 WebSocket 存在的云函数 +::: 以下是云函数中处理 WebSocket 示例: + ```ts export async function main(ctx: FunctionContext) { if (ctx.method === "WebSocket:connection") { ctx.socket.send('hi connection succeed') - } if (ctx.method === 'WebSocket:message') { @@ -24,9 +29,10 @@ export async function main(ctx: FunctionContext) { } ``` -更多用法请参考: https://github.com/websockets/ws +更多用法请参考: ## 客户端 WebSocket 连接 + ```ts const wss = new WebSocket("wss://your-own-appid.laf.run/__websocket__"); @@ -43,4 +49,83 @@ wss.onmessage = (res) => { wss.onclose = () => { console.log("closed"); }; -``` \ No newline at end of file +``` + +## 连接演示 + +1、替换 `__websocket__` 代码并发布 + +```js +import cloud from '@lafjs/cloud' + +export async function main(ctx: FunctionContext) { + // 初始化websocket user Map列表 + // 也可用数据库保存,本示例代码用的Laf云函数的全局缓存 + let wsMap = await cloud.shared.get("wsMap") // 获取wsMap + if(!wsMap){ + wsMap = new Map() + await cloud.shared.set("wsMap", wsMap) // 设置wsMap + } + // websocket 连接成功 + if (ctx.method === "WebSocket:connection") { + const userId = generateUserId() + wsMap = await cloud.shared.get("wsMap") // 获取wsMap + wsMap.set(userId, ctx.socket); + await cloud.shared.set("wsMap", wsMap) // 设置wsMap + ctx.socket.send("连接成功,你的userID是:"+userId); + } + + // websocket 消息事件 + if (ctx.method === "WebSocket:message") { + const { data } = ctx.params; + console.log("接收到的信息:",data.toString()); + const userId = getKeyByValue(wsMap, ctx.socket); + ctx.socket.send("服务端已接收到消息事件,你的userID是:"+userId); + } + + // websocket 关闭消息 + if (ctx.method === "WebSocket:close") { + wsMap = await cloud.shared.get("wsMap") // 获取wsMap + const userId = getKeyByValue(wsMap, ctx.socket); + wsMap.delete(userId); + await cloud.shared.set("wsMap", wsMap) // 设置wsMap + ctx.socket.send("服务端已接收到关闭事件消息,你的userID是:"+userId); + } +} +// 生成随机用户ID +function generateUserId() { + return Math.random().toString(36).substring(2, 15); +} + +// 遍历userID +function getKeyByValue(map, value) { + for (const [key, val] of map.entries()) { + if (val === value) { + return key; + } + } +} +``` + +2、获得你的WebSocket链接 + +一般格式为: + +your-own-appid换成你的Laf应用appid + +`wss://your-own-appid.laf.run/__websocket__` + +3、客户端链接WebSocket链接,并获得 userID + +4、新建云函数通过userID去推送消息 + +```js +import cloud from '@lafjs/cloud' + +export async function main(ctx: FunctionContext) { + const userID = '' + let wsMap = await cloud.shared.get("wsMap") + ctx.socket = wsMap.get(userID) + ctx.socket.send("消息测试"); +} +```