使用 t1yOS 云函数之前,你需要先前往:控制台(console.t1y.net)创建一个应用,并打开该应用,随后在云操作系统页面中打开“文件 App“,并在“文件 App“ 中打开“云函数(.functions)“文件夹,在空白区域右键“新建云函数“,取名“index“,再次右键“在代码编辑器中打开“,再次点击右上角绿色的运行按钮,即可看到云函数执行结果。
当前文档仅仅只是云函数的一些常用用法,对于日常开发已足够,云函数还有非常非常多的功能,请参阅:云函数高阶文档
在云函数中引入 context 模块,即可操作 HTTP 请求响应上下文:
const ctx = require('context')
使用 http 模块可以使用云函数已定义常量,如:请求方法、状态码等,详情参阅:常量定义文档
const http = require('http')
每个应用都有一个独立的文件系统,文件系统相当于一个 Web 服务器(类似 Nginx+PHP),可对外提供静态资源以及云函数访问。所有的云函数都必须放到云函数(.functions)目录下,支持二级目录,文件拓展名必须为 .jsc。
├── /(根目录)
│ └── .functions(云函数目录)
│ └── apps(App Center 中安装的云函数应用)
│ └── helloworld(示列应用)
│ └── .private(私有目录)
├── index.html
注意:所有的 . 开头的文件或文件夹对外都是禁止访问的,敏感文件可使用隐藏文件命名或放到 .private 目录下,如:微信、支付宝等密钥文件。
云函数支持 GET、POST、PUT、PATCH、DELETE 等请求方式,当请求到达云函数时,会触发执行 onRequest 函数,支持返回任意类型的数据。如 Object、String、Number、Boolean 等,当然云函数还支持自定义状态码、返回文件、JSON、JSONP、CBOR、msgPack、XML、重定向、渲染网页等。
function onRequest() {
return 'Hello, World!'
}
注意:云函数必须放到云函数(.functions)文件夹内,才能生效;并且支持二级目录,与普通 Web 服务器无异,但云函数性能非常强。
新创建的云函数默认是一个获取问候语的云函数,代码如下所示:
const ctx = require('context')
const http = require('http')
function onRequest() {
console.log('this is a console message.')
return {
code: http.StatusOK,
message: `Hello ${ctx.query('name', 'WangHua')}, ${getGreeting()}!`,
data: null,
}
}
function getGreeting() {
const hour = new Date().getHours()
if (hour >= 5 && hour < 12) {
return 'Good morning'
} else if (hour >= 12 && hour < 18) {
return 'Good afternoon'
} else if (hour >= 18 && hour < 23) {
return 'Good evening'
} else {
return 'Good night'
}
}
请求云函数访问链接:https://myapp.t1y.net/<YourAppID>/index.jsc?name=Zhangsan
响应 200 OK:
{
code: 200,
message: "Hello Zhangsan, Good morning!",
data: null
}
当你请求该云函数时,将执行 onRequest 入口函数,可以获得 JSON 对象问候响应,你还可以在云操作系统页面中的日志 App中看到 this is a console message. 日志输出。
你可以使用 console 中的 log、info、debug、warn、error 打印不同级别的日志,这些日志都能够在云操作系统页面中的日志 App 中看到。例如:
function onRequest() {
console.log('this is a console message.')
console.info('this is a console message.')
console.debug('this is a console message.')
console.warn('this is a console message.')
console.error('this is a console message.')
return null
}
文件:/.functions/pkg/response.jsc
module.exports = {
fail: (code, message, data) => {
return { code: code, message: message, data: data }
},
success: (message, data) => {
return { code: http.StatusOK, message: message, data: data }
},
}
文件:index.jsc
const http = require('http')
const resp = require('/pkg/response.jsc') // 导入云函数模块无需 /.functions/pkg/response.jsc 这样的完整路径,导入 json 文件必须使用完整路径
function onRequest() {
return resp.success('ok', null)
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:{"code": 200, "message": "ok", "data": null}
导入 JSON 文件
const data = require('/.private/data.json') // 导入有效的 JSON 文件,系统会自动转换为 Object 类型
function onRequest() {
return data.name
}
导入远程 JavaScript 文件和 JSON 文件
云函数支持导入普通的纯 JavaScript 文件,可以使用例如:CryptoJS 等现有库或自己封装,更好的辅助用户开发。同时也支持导入本地的 JavaScript 文件或 JSON 文件。
clearCache 函数清除缓存// const CryptoJS = require('/.librarys/crypto-js.min.js') // 本地导入
// const PackageJSON = require('/.librarys/package.json') // 本地导入
// 远程导入 JavaScript 和 JSON:
const CryptoJS = require('https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js')
const PackageJSON = require('https://unpkg.com/vue/package.json')
function onRequest() {
clearCache() // 如果导入的 JavaScript 或者 JSON 文件发生变化可手动清理缓存(需按需调用,文件没有变化的情况下不需要清理,存在缓存访问加载速度会更快)
return { name: PackageJSON.name, md5: CryptoJS.MD5('Hello, World!').toString() }
}
导入模块时,只有普通的 .js 文件或 .json 文件会被缓存,云函数内置模块以及 .jsc 云函数文件都是预编译的,不会被缓存,修改后会立即生效,推荐使用 .jsc 云函数文件进行封装,预编译,这样执行效率会更快一些。
使用 ctx.userAgent 函数,你可以获取客户端的身份标识信息,如设备类型等。
const ctx = require('context')
function onRequest() {
return ctx.userAgent()
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36
使用 ctx.ip 函数可以获取客户端请求的 IP 地址:
const ctx = require('context')
function onRequest() {
return ctx.ip()
}
使用 ctx.ips() 函数,可以获取 X-Forwarded-For 请求头中指定的 IP 地址数组。
const ctx = require('context')
function onRequest() {
return ctx.ips()
}
t1yOS 云函数支持 GET、POST、PUT、PATCH、DELETE 等请求方法,因此可使用 method 函数获取。
const ctx = require('context')
function onRequest() {
return ctx.method()
}
GET 请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:GET
POST 请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:POST
...
例子:
const ctx = require('context')
function onRequest() {
if (ctx.method() == 'GET') {
return 'Hello'
}
return '仅允许使用 GET 请求'
}
GET 请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:Hello
POST 请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:仅允许使用 GET 请求
使用 ctx.get 函数获取请求头,该函数支持默认值(可选),当前获取的请求头不存在时返回默认值。
const ctx = require('context')
function onRequest() {
return `你的 IP 地址是:${ctx.get('x-forwarded-for')}`
}
默认值:
const ctx = require('context')
function onRequest() {
return `你的 IP 地址是:${ctx.get('x-forwarded-for', '127.0.0.1')}`
}
除此之外,你还能使用 ctx.getReqHeaders 函数获取全部请求头,该函数返回一个 Object 对象:
const ctx = require('context')
function onRequest() {
const headers = ctx.getReqHeaders()
return `你的 IP 地址是:${headers['X-Forwarded-For'][0]}`
}
最后你还能使用 ctx.hasHeader 函数判断一个请求头是否存在。
使用 ctx.set 函数可以将响应头设置为指定的 key 和 value。
const ctx = require('context')
function onRequest() {
ctx.set('Content-Type', 'text/plain')
return 'Hello, World!'
}
获取查询参数所谓的意思就是在请求链接 ? 后的 key-value 值,如:https://myapp.t1y.net/<YourAppID>/index.jsc?name=Zhangsan&age=33&xxx=xxx
const ctx = require('context')
function onRequest() {
return ctx.query('name')
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc?name=Zhangsan
响应:Zhangsan
使用 ctx.query 即可获取,该函数支持默认值(可选),如果 name 值不存在时,则返回默认值。
默认值:
const ctx = require('context')
function onRequest() {
return ctx.query('name', 'WangHua')
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc
响应:WangHua
请求:https://myapp.t1y.net/<YourAppID>/index.jsc?name=Zhangsan
响应:Zhangsan
除此之外你还能使用 ctx.queries 函数获取全部查询参数,该函数返回一个 Object 对象:
const ctx = require('context')
function onRequest() {
return ctx.queries().name
}
使用 ctx.formValue 函数可获取 POST 表单中的数据。
const ctx = require('context')
function onRequest() {
return ctx.formValue('name')
}
默认值(可选):
const ctx = require('context')
function onRequest() {
return ctx.formValue('name', 'WangHua')
}
同样,你也可以使用 ctx.isForm 函数判断是不是表单。
除了 GET 请求外,POST、PUT、PATCH、DELETE 都可以使用 ctx.body() 函数获取请求体中的内容。如果请求体中是有效的 JSON 对象,那么在云函数中会自动转换为 Object 类型。
const ctx = require('context')
function onRequest() {
if (ctx.method() != 'GET') {
const body = ctx.body()
return body.name
}
return null
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc
Body:{"name": "WangHua", "age": 23}
响应:WangHua
例子:
const ctx = require('context')
function onRequest() {
if (ctx.method() != 'GET') {
return ctx.body()
}
return null
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc
Body:你好!
响应:你好!
除此之外,你还可以使用 ctx.hasBody 函数判断存不存在请求体,如:
const ctx = require('context')
function onRequest() {
if (ctx.method() != 'GET') {
if (ctx.hasBody()) {
return ctx.body()
}
return '请求体不能为空'
}
return null
}
请求:https://myapp.t1y.net/<YourAppID>/index.jsc
Body:null
响应:请求体不能为空
另外,你还能使用 ctx.isJSON 函数判断当前请求 Content-Type 是不是 application/json。
使用 ctx.redirect 函数可以将云函数重定向至指定 url:
const ctx = require('context')
function onRequest() {
ctx.redirect(302, 'https://baidu.com/') // 临时重定向
return null
}
临时重定向(使用 HTTP 状态码常量)
const ctx = require('context')
const http = require('http')
function onRequest() {
ctx.redirect(http.StatusFound, 'https://baidu.com/')
return null
}
永久重定向(使用 HTTP 状态码常量)
const ctx = require('context')
const http = require('http')
function onRequest() {
ctx.redirect(http.StatusMovedPermanently, 'https://baidu.com/')
return null
}
使用 ctx.render 函数可以将模板渲染为网页。
const ctx = require('context')
function onRequest() {
ctx.render('/.templates/index.tmpl', {
Title: 'My Website',
User: { ID: 1001, Name: 'WangHua', Age: 23 },
Version: '1.0',
})
return null
}
/.templates/index.tmpl
<html>
<head>
<title>{{ .Title }}</title>
</head>
<body>
<h1>{{ .User.Name }}</h1>
<p>ID: {{ .User.ID }}</p>
<p>Age: {{ .User.Age }}</p>
<p>Version: {{ .Version }}</p>
</body>
</html>
你可以使用 ctx.locals 存储和获取当前请求生命周期中的一些值,该存储仅在当前请求生命周期内有效。
const ctx = require('context')
function onRequest() {
// 存储
ctx.locals('uid', 123456)
ctx.locals('name', 'WangHua')
ctx.locals('age', 23)
ctx.locals('is_active', true)
// 获取
ctx.locals('uid') // 返回 123456
ctx.locals('name') // 返回 WangHua
ctx.locals('age') // 返回 23
ctx.locals('is_active') // 返回 true
return null
}
使用 ctx.cookie 函数可以设置一个 Cookie:
可接受的参数:
{
/** Cookie 的名称 */
name: string
/** Cookie 的值 */
value: string
/** 允许接收该 Cookie 的 URL 路径 */
path: string
/** 允许接收该 Cookie 的域名 */
domain: string
/** Cookie 最大有效期(单位:秒) */
max_age: number
/** Cookie 过期时间 */
expires: Date
/** 是否仅通过 HTTPS 传输(安全连接) */
secure: boolean
/** 是否仅允许 HTTP 访问(JS 无法读取) */
http_only: boolean
/** 控制是否允许跨站请求携带 Cookie */
same_site: string
/** 是否启用分区 Cookie 存储 */
partitioned: boolean
/** 是否为会话 Cookie(关闭浏览器即失效) */
session_only: boolean
}
例子:
const ctx = require('context')
function onRequest() {
ctx.cookie({ name: 'john', value: 'doe', expires: new Date(Date.now() + 24 * 60 * 60 * 1000) })
return null
}
使用 ctx.cookies 函数可以获取指定 Cookie,该函数支持默认值(可选):
const ctx = require('context')
function onRequest() {
// 通过 Key 获取 Cookie:
ctx.cookies('name') // 'john'
ctx.cookies('empty', 'doe') // 'doe'
return null
}
使用 ctx.clearCookie 函数可以清空客户端 Cookie 或指定 Cookie(可选),使 Cookie 过期。
const ctx = require('context')
function onRequest() {
ctx.ClearCookie() // 清除所有 Cookie
ctx.ClearCookie('user') // 按名称使特定 Cookie 过期
ctx.ClearCookie('token', 'session', 'track_id', 'version') // 按名称使多个 Cookie 过期
return null
}
使用 ctx.formFileInfo 可获取上传的文件信息,使用 ctx.saveFile 可保存文件至文件系统。注意:ctx.formFileInfo 返回的是一个 Object 对象,包含 filename、size、header。
const ctx = require('context')
function onRequest() {
const fileInfo = ctx.formFileInfo('file')
ctx.saveFile('file', `/.private/uploads/${fileInfo.filename}`)
return '上传成功'
}
使用 ctx.download 函数可以让浏览器访问时自动触发下载。
const ctx = require('context')
function onRequest() {
ctx.download('/.private/uploads/w.png') // 下载私有目录中的文件
return null
}
const ctx = require('context')
function onRequest() {
ctx.sendFile('/.private/uploads/w.png') // 发送私有目录中的文件
return null
}
使用 ctx.XML 函数可以响应 XML 数据。
const ctx = require('context')
function onRequest() {
const data = { name: 'WangHua', age: 23 }
ctx.XML(data)
return null
}
JSONP(JSON with Padding)是一种跨域请求数据的早期解决方案,你可以使用 ctx.jsonp 函数发送数据。
const ctx = require('context')
function onRequest() {
const data = { name: 'WangHua', age: 23 }
ctx.jsonp(data)
return null
}
响应:callback({"name": "WangHua", "age": 23});
自定义 callback:
const ctx = require('context')
function onRequest() {
const data = { name: 'WangHua', age: 23 }
ctx.jsonp(data, 'customFunc')
return null
}
响应:customFunc({"name": "WangHua", "age": 23});
使用 ctx.sendStatus 函数可以发送自定义的状态码(响应码)。除了使用数字之外,你还可以使用云函数 http 包中已经定义的常量。具体定义请参考:常量定义文档。
const ctx = require('context')
function onRequest() {
ctx.sendStatus(200) // HTTP 200 OK
return null
}
使用常量:
const ctx = require('context')
const http = require('http')
function onRequest() {
ctx.sendStatus(http.StatusInternalServerError) // 服务器内部错误
return null
}
生成 JWT Token:
jwt.generateToken(userId: string, roles: object, expiresAt: number) // number 单位:分钟
const jwt = require('jwt')
function onRequest() {
const MINUTES_7_DAYS = 7 * 24 * 60 // 7 天有效期
const token = jwt.generateToken('uid123', ['user', 'super_admin'], MINUTES_7_DAYS)
return token
}
验证 JWT Token:
const ctx = require('context')
const http = require('http')
const jwt = require('jwt')
function onRequest() {
const payload = jwt.verifyToken() // 只需调用即可,系统会自动进行判断是否有效
if (payload.userId != 'uid123') {
ctx.sendStatus(http.StatusUnauthorized)
return { code: http.StatusUnauthorized, message: '权限不足', data: null }
}
if (!payload.roles.includes('super_admin')) {
ctx.sendStatus(http.StatusUnauthorized)
return { code: http.StatusUnauthorized, message: '权限不足', data: null }
}
return `Hello, ${payload.userId}!`
}
云函数可以使用 http 包中的 send 函数发送网络请求。如果响应内容是有效的 JSON 对象,那么会自动转换为 Object 类型。
函数签名:
http.send(method: string, url: string, headers: object, body: string)
函数返回:
调用该函数会返回一个 Object 对象,包含以下内容:
不带请求头和请求体例子(响应是有效 JSON 对象)
const http = require('http')
function onRequest() {
const resp = http.send('GET', 'https://myapp.t1y.net/timestamp', {}, '')
if (resp.statusCode != http.StatusOK) {
return '请求成功,但状态码不是 HTTP 200 OK'
}
return resp.body.data.unix
}
带请求头和请求体例子(响应是有效 JSON 对象)
const http = require('http')
function onRequest() {
const reqBody = { name: 'WangHua', age: '23' }
const resp = http.send(
'POST',
'https://myapp.t1y.net/timestamp',
{ Authorization: 'Bearer <JWT_TOKEN>' },
JSON.stringify(reqBody)
)
if (resp.statusCode != http.StatusOK) {
return '请求成功,但状态码不是 HTTP 200 OK'
}
return resp.body.data.unix
}
使用 os 包可以操作环境变量。
获取环境变量:
const os = require('os')
function onRequest() {
return os.getEnv('API_KEY')
}
设置环境变量:
const os = require('os')
function onRequest() {
os.setEnv('API_KEY', 'sk-xxx')
return null
}
取消(删除)环境变量:
const os = require('os')
function onRequest() {
os.unSetEnv('API_KEY')
return null
}
使用 os 包可以操作文件系统中的文件。
读取文件:
utf-8、gb18030、gbk 三种编码格式const os = require('os')
function onRequest() {
const content = os.readFile('/.private/a.txt', 'utf-8', {})
return content
}
带 checksum 校验读取:
const os = require('os')
function onRequest() {
const opts = {
verify_checksum: true, // 是否启用校验
expected_checksum: '5d41402abc4b2a76b9719d911017c592', // 期望 md5(用于一致性验证)
}
const content = os.readFile('/.private/a.txt', 'utf-8', opts)
return content
}
写入文件:
opts 结构:{
atomic?: boolean, // 是否原子写(默认 true)
sync?: boolean, // 是否 fsync 落盘
return_checksum?: boolean // 是否返回 md5
}
{
bytes_written: number,
checksum: string
}
普通写入文件:
const os = require('os')
function onRequest() {
const res = os.writeFile('/.private/hello.txt', 'hello world', {})
return res
}
原子写入(推荐):
const os = require('os')
function onRequest() {
const res = os.writeFile('/.private/user.json', JSON.stringify({ id: 1, name: 'WangHua' }), {
atomic: true,
})
return res
}
强制落盘:
const os = require('os')
function onRequest() {
const res = os.writeFile('/.private/log.txt', 'critical log', {
sync: true,
})
return res
}
获取 checksum:
const os = require('os')
function onRequest() {
const res = os.writeFile('/.private/a.txt', 'content', {
return_checksum: true,
})
return res.checksum
}
云函数可以使用 crypto 包中提供的常用加解密函数完成诸如:md5、sha1、sha256、hmac_sha256、hmac_sha512、base64、bcrypt、argon2id、aes 等加解密场景下的使用。具体内容请参考:常用加解密函数文档
在云操作系统页面中的设置 App 中可以开启安全模式,开启安全模式后请求和响应都需要采用 AES 加密算法进行动态加密,有效防止中间人抓包攻击和重放攻击。开启后有如下特点:
AES 动态加密,杜绝中间人窃听。注意:设置 App 中有没有开启安全模式都不会对云函数造成影响(只会对 RESTful API 生效),云函数需手动调用 ctx.enableSafeMode 该函数显式开启,例如:
const ctx = require('context')
function onRequest() {
ctx.enableSafeMode() // 手动调用时,该云函数的所有请求和响应内容都会加密(请求体需遵循规则进行加密处理,响应体则会自动加密)
return null
}
调用 ctx.sign 函数可以对当前的请求进行签名验证,如果签名不正确可以拒绝,该函数和 ctx.enableSafeMode() 函数搭配可以获得最好的效果,但需按需选择。调用该函数验证签名有如下特点:
HMAC 加密算法对每个请求进行签名,确保请求来源可信、内容完整未被篡改。10 秒内有效,超时自动拒绝,有效防御重放攻击。注意:云函数需手动调用 ctx.sign 函数验证签名,而 RESTful API 则是默认开启且不可关闭,确保请求来源可信、内容完整未被篡改。
const ctx = require('context')
function onRequest() {
if (!ctx.sign()) {
return '签名验证失败'
}
return '你好!'
}
请求签名和安全模式下的 AES 动态加解密规则见:签名文档