Skip to content

云函数内置 API ​

本页集中整理 Cloud Function 沙盒中可用的内置 API。主 Cloud Function API 页面保留鉴权、调用接口和错误响应;这里按对象查方法、参数、返回值和示例。

速查 ​

对象适合场景常用方法
$data读写 Data API 数据合集get、set、list、count、top、around、me、update、delete、inc、createOnce、incAtomic、transaction
$permission查询和维护角色、权限listRoles、getUserRoles、checkPermission、assignRole、removeRole
$account获取账号只读信息getUser、findUser、getAvatar
$storage上传、下载、列出和删除文件getUploadUrl、confirmUpload、getDownloadUrl、list、delete
$crypto加密解密、摘要、HMAC、ES256 JWSencrypt、decrypt、hash、hmac、jwsSign、jwsVerify
$certificates固定引用应用级 JWS/X.509 发布策略policy、hasPolicy
$secrets只写 Secret Store 的不透明引用ref、has、names
$appleIapApple 服务端 API Token、交易查询与通知薄封装createApiToken、getTransactionInfo、verifyNotification
$invoke云函数之间同步调用$invoke(functionNameOrPath, params?, folder?)
fetch调用公网 HTTP/HTTPS 服务fetch(url, options)

跨应用访问统一使用 withBasic(clientId, clientSecret)。目标应用的 client_secret 应放在当前云函数的环境变量中;需要以同一个登录用户身份访问目标应用时,再链式调用 asUser(uid)。

跨应用访问 ​

默认情况下,$data、$permission、$storage 和 $invoke 都访问当前云函数所属应用的资源。需要访问其他应用时,可以使用 withBasic(clientId, clientSecret) 创建目标应用客户端。平台会使用传入的 Client ID 和 Client Secret 做 Basic 认证,认证通过后以目标应用的应用身份执行操作。

如果目标应用的合集、权限、存储或被调用云函数需要按用户身份执行,可以在当前应用或 withBasic 返回的客户端上调用 asUser(uid)。这里的 uid 是平台全局用户 ID,适合跨应用访问“同一个用户”的数据;它只影响这一次桥接调用,不会修改当前云函数里的 caller。

javascript
const targetClientId = env.PARTNER_APP_ID
const targetClientSecret = env.PARTNER_APP_SECRET

const partnerData = $data.withBasic(targetClientId, targetClientSecret)
const partnerPermission = $permission.withBasic(targetClientId, targetClientSecret)
const partnerStorage = $storage.withBasic(targetClientId, targetClientSecret)
const invokePartner = $invoke.withBasic(targetClientId, targetClientSecret)
const partnerUid = params.partnerUid || caller.uid

const profile = await partnerData.asUser(partnerUid).get('profiles', partnerUid)
const perm = await partnerPermission.asUser(partnerUid).checkPermission(partnerUid, 'trade.exchange')
const file = await partnerStorage.asUser(partnerUid).getDownloadUrl('public/rules.json')
const result = await invokePartner.asUser(partnerUid)('shared_exchange_rate', { from: 'gold', to: 'diamond' })

return { profile: profile.document, allowed: perm.hasPermission, fileUrl: file.url, result }

安全提示:目标应用的 client_secret 应放在当前云函数的环境变量中,不能写入客户端或硬编码在函数源码里。目标应用也必须已审核通过,并开通对应服务(Data、Permission、Storage 或 Cloud Function)。

$data — 数据合集桥接 ​

$data 允许云函数直接访问 Data API 的数据合集,无需发起 HTTP 请求。使用前需要在管理面板中为应用开启 Data 服务。

所有方法返回 Promise,支持 await。

方法说明
$data.get(collection, documentId)获取单个文档
$data.set(collection, documentId, data, ownerUid?)创建或覆盖文档
$data.list(collection, options?)列出文档(支持排序和条件过滤)
$data.count(collection, options?)统计文档数量(支持条件过滤)
$data.top(collection, indexName, options?)按有序索引读取前排文档,适合排行榜
$data.around(collection, indexName, documentId, options?)按有序索引读取指定文档所在的排名窗口
$data.me(collection, indexName, options?)按有序索引读取当前用户所在的排名窗口
$data.update(collection, documentId, data)部分更新文档(支持操作符)
$data.delete(collection, documentId)删除文档
$data.inc(collection, documentId, fields)原子递增($data.update 的 $inc 快捷方式)
$data.createOnce(collection, documentId, data, ownerUid?)幂等创建文档;文档已存在时不覆盖,返回 created:false
$data.incAtomic(collection, documentId, fields, options?)在数据库事务内锁定文档并递增数值字段,支持点路径嵌套递增
$data.transaction(async tx => { ... })执行一组事务操作;事务客户端支持 get、set、update、delete、inc、createOnce 和 incAtomic
$data.withBasic(clientId, clientSecret)创建访问其他应用 Data 服务的客户端,返回同样的方法集合
$data.asUser(uid)以指定用户 UID 访问当前应用 Data 服务;也可链在 withBasic() 返回的客户端后访问目标应用

options 参数(用于 list 和 count):

字段类型说明
ownerUidstring按所有者筛选
limitnumber每页条数(默认 50,最大 100)
offsetnumber偏移量
sortBystring按 data 中的字段排序(如 'score')
sortOrderstring排序方向:'asc' 或 'desc'(默认 'desc')
whereobject条件过滤(见下文)

有序索引 options(用于 top / around / me):

字段类型说明
limitnumbertop 返回条数,默认 50,最大 100
offsetnumbertop 分页偏移
ownerUidstringtop 按单个 owner 筛选
ownerUidsstring | string[]top 按多个 owner 筛选,适合好友榜
partitionobject按索引分区筛选,例如 { groupId, variant }
beforenumberaround / me 返回目标前多少名
afternumberaround / me 返回目标后多少名

where 条件过滤:

对文档 data JSON 中的字段进行过滤。支持两种形式:

形式语法示例
等值匹配{ field: value }{ level: 10 } — 精确匹配
操作符{ field: { $op: value } }{ score: { $gt: 1000 } } — 大于

操作符: $eq(等于)、$ne(不等于)、$gt(大于)、$gte(大于等于)、$lt(小于)、$lte(小于等于)

多个条件以 AND 逻辑组合:

javascript
// 单一等值匹配
await $data.list('players', { where: { level: 10 } })

// 多条件组合 (AND)
await $data.list('leaderboard', {
  where: {
    score: { $gt: 5000 },
    tier: 'premium',
  },
  sortBy: 'score',
  sortOrder: 'desc',
})

// 范围查询
await $data.list('products', {
  where: {
    price: { $gte: 100, $lte: 500 },
  },
})

// 带条件的统计
const { count } = await $data.count('players', {
  where: { level: { $gt: 5 } },
})

where 过滤对 data JSON 列操作。字段名限制为 [a-zA-Z_][a-zA-Z0-9_]{0,63} 以防止 SQL 注入。

返回值:

方法返回内容说明
$data.get(){ document: { documentId, ownerUid, data, createdAt, updatedAt } }完整文档(含 data 内容)
$data.list(){ documents[], total, limit, offset }仅元数据 — 每个文档包含 documentId、ownerUid、createdAt、updatedAt(不含 data)
$data.count(){ count }匹配文档的数量

注意: $data.list() 不会返回 data 字段以节省带宽和性能。请使用 $data.get(collection, documentId) 获取文档的完整内容。

如果你需要先分页/排序,再读取文档内容,可以先用 $data.list() 拿到 documentId,再逐条 $data.get():

javascript
const page = await $data.list('leaderboard', {
  sortBy: 'score',
  sortOrder: 'desc',
  limit: 10,
})

const items = await Promise.all(page.documents.map(async ref => {
  const full = await $data.get('leaderboard', ref.documentId).catch(() => null)
  return full?.document
    ? { documentId: ref.documentId, ownerUid: ref.ownerUid, ...full.document.data }
    : null
}))

return { items: items.filter(Boolean), total: page.total }

update 操作符:

$data.update 的 data 参数支持操作符语法,实现原子更新:

操作符说明示例
$set设置字段值{ $set: { name: 'new' } }
$unset删除字段{ $unset: { tempField: '' } }
$inc数字递增(负数为递减){ $inc: { score: 10, lives: -1 } }
$push向数组末尾追加元素{ $push: { tags: 'new_tag' } }
$pull从数组中移除匹配元素{ $pull: { tags: 'old_tag' } }

当 data 参数中包含 $ 开头的键时,自动进入操作符模式。操作符可以组合使用:

javascript
await $data.update('players', 'player_001', {
  $inc: { score: 50, combo: 1 },
  $push: { achievements: 'first_win' },
  $set: { lastPlayedAt: new Date().toISOString() },
})

快捷方式:$data.inc(collection, documentId, fields) 等价于 $data.update(collection, documentId, { $inc: fields })

幂等 / 事务方法:

方法适合场景行为
$data.createOnce(collection, documentId, data, ownerUid?)第三方事件去重、订单事件记录、一次性兑换码文档不存在时创建;文档已存在时不覆盖、不报错
$data.incAtomic(collection, documentId, fields, options?)钱包余额、库存、计数器、奖励发放在事务中加写锁后递增字段;字段不存在按 0 处理
$data.transaction(async tx => { ... })支付到账、发货、扣库存等需要一组操作同时成功/失败的逻辑callback 成功时提交,抛错时回滚

incAtomic 的 fields 支持点路径:

javascript
await $data.incAtomic('wallets', caller.uid, {
  balance: 100,
  'stats.totalPaid': 100,
}, {
  ownerUid: caller.uid,
  createIfMissing: true,
})

options 字段:

字段类型说明
ownerUidstring创建新文档时使用的 owner uid
createIfMissingboolean文档不存在时是否创建;默认 false

幂等发货示例:

javascript
const eventId = String(params.eventId || '')
const uid = String(params.uid || '')
const amount = Number(params.amount || 0)

if (!eventId || !uid || amount <= 0) return { error: 'invalid_params' }

await $data.transaction(async tx => {
  const event = await tx.createOnce('payment_events', eventId, {
    uid,
    amount,
    createdAt: Date.now(),
  })

  // 重复投递时 createOnce 不覆盖旧记录,直接跳过发货
  if (!event.created) return

  await tx.incAtomic('wallets', uid, { balance: amount }, {
    ownerUid: uid,
    createIfMissing: true,
  })
})

return { ok: true }

事务客户端支持 get、set、update、delete、inc、createOnce 和 incAtomic。需要锁定余额、库存等数值字段时,优先使用 incAtomic;它会在事务内对目标文档加写锁。不要把长时间网络请求放进事务 callback 内;需要调用第三方接口时,先在事务外完成验签和数据解析,再进入事务写库。

跨应用事务应先创建目标应用客户端,再调用 transaction:

javascript
await $data.withBasic(env.TARGET_CLIENT_ID, env.TARGET_CLIENT_SECRET).transaction(async tx => {
  await tx.createOnce('events', params.eventId, { createdAt: Date.now() })
})

不要在事务 callback 内再调用 tx.withBasic(...) 切换到另一个应用。

字段不存在时的行为:

写法文档不存在字段不存在
$data.set(collection, documentId, data, ownerUid?)自动创建文档整个 data 替换为传入对象;不会因为缺字段报错
$data.update(collection, documentId, { field: value })报 not_found自动新增顶层字段
$data.update(..., { $set: { field: value } })报 not_found自动新增顶层字段
$data.update(..., { $inc: { field: 1 } })报 not_found按 0 + 1 写入;$inc 的值必须是数字
$data.update(..., { $push: { field: value } })报 not_found自动创建数组并追加元素;原字段不是数组时按空数组处理
$data.update(..., { $pull: { field: value } })报 not_found字段不存在或不是数组时不报错,等同无操作

$data.update 是顶层字段更新。{ $set: { 'profile.name': 'A' } } 会写入一个名为 "profile.name" 的顶层字段,不会自动深入 profile 对象。

嵌套对象更新:

当前推荐在云函数中读出文档、修改嵌套对象,然后把变化所在的顶层字段写回:

javascript
function cloneJson(value) {
  return JSON.parse(JSON.stringify(value || {}))
}

function setNested(data, path, value) {
  const keys = String(path).split('.').filter(Boolean)
  if (!keys.length) throw new Error('path is required')

  let cursor = data
  for (let i = 0; i < keys.length - 1; i++) {
    const key = keys[i]
    if (!cursor[key] || typeof cursor[key] !== 'object' || Array.isArray(cursor[key])) {
      cursor[key] = {}
    }
    cursor = cursor[key]
  }

  cursor[keys[keys.length - 1]] = value
  return keys[0]
}

async function updateNested(collection, documentId, path, value) {
  const { document } = await $data.get(collection, documentId)
  const data = cloneJson(document.data)
  const rootKey = setNested(data, path, value)

  await $data.update(collection, documentId, {
    $set: { [rootKey]: data[rootKey] },
  })

  return { status: 'ok', documentId, path }
}

await updateNested('player_profile', caller.uid, 'settings.audio.bgmVolume', 80)

这种方式会重写变化所在的顶层对象。如果多个云函数同时修改同一个顶层对象,例如都在改 settings.*,后写入可能覆盖先写入。高并发数据建议拆成更小的顶层字段或独立文档。

示例:防作弊分数上传 ​

javascript
// 分数在服务端计算,客户端无法篡改

const { level, combo, accuracy, playerUid } = params

// 验证参数
if (!level || !combo || accuracy === undefined || !playerUid) {
  return { error: 'missing_params' }
}

// 服务端计算分数
const score = level * 1000 + combo * 50 + Math.floor(accuracy * 500)

// 获取历史最高分
const existing = await $data.get('leaderboard', playerUid).catch(() => null)
const existingData = existing?.document?.data
if (existingData?.score >= score) {
  return { updated: false, currentBest: existingData.score }
}

// 写入新纪录
await $data.set('leaderboard', playerUid, {
  score,
  level,
  combo,
  accuracy,
  timestamp: new Date().toISOString(),
}, playerUid)

console.log(`Player ${playerUid} new high score: ${score}`)
return { updated: true, score }

示例:用户配置存储 ​

javascript
if (params.action === 'save') {
  await $data.set('settings', caller.uid, {
    theme: params.theme || 'default',
    language: params.language || 'zh-CN',
    notifications: params.notifications !== false,
  })
  return { saved: true }
} else {
  const doc = await $data.get('settings', caller.uid).catch(() => null)
  return doc?.document?.data || { theme: 'default', language: 'zh-CN', notifications: true }
}

示例:原子操作(计数器 / 收藏夹) ​

javascript
const { playerUid, action, itemId } = params

if (action === 'like') {
  // 点赞数 +1,原子操作
  await $data.update('posts', params.postId, {
    $inc: { likes: 1 },
  })
  return { success: true }
}

if (action === 'collect') {
  // 向用户收藏夹添加物品
  await $data.update('favorites', playerUid, {
    $push: { items: itemId },
    $inc: { count: 1 },
  })
  return { success: true }
}

if (action === 'uncollect') {
  // 从收藏夹移除物品
  await $data.update('favorites', playerUid, {
    $pull: { items: itemId },
    $inc: { count: -1 },
  })
  return { success: true }
}

示例:排行榜查询 ​

javascript
// 获取前 10 名玩家(按分数降序)
const topPage = await $data.list('leaderboard', {
  sortBy: 'score',
  sortOrder: 'desc',
  limit: 10,
})
const top10 = await Promise.all(topPage.documents.map(async ref => {
  const full = await $data.get('leaderboard', ref.documentId).catch(() => null)
  return full?.document
    ? { documentId: ref.documentId, ...full.document.data }
    : null
}))

// 多字段组合筛选:查找高分且高级会员玩家
const elitePage = await $data.list('leaderboard', {
  where: {
    score: { $gt: 5000 },
    tier: 'premium',
  },
  sortBy: 'score',
  sortOrder: 'desc',
})
const elitePlayers = await Promise.all(elitePage.documents.map(async ref => {
  const full = await $data.get('leaderboard', ref.documentId).catch(() => null)
  return full?.document
    ? { documentId: ref.documentId, ...full.document.data }
    : null
}))

return {
  top10: top10.filter(Boolean),
  elitePlayers: elitePlayers.filter(Boolean),
}

$permission — 权限组桥接 ​

$permission 允许云函数直接访问 Permission API 的角色和权限数据,无需发起 HTTP 请求。使用前需要在管理面板中为应用开启 Permission 服务。

所有方法返回 Promise,支持 await。

方法说明
$permission.listRoles()列出所有角色
$permission.getRole(name)获取角色详情
$permission.listRoleMembers(roleName, { page, limit })分页列出某个角色下的成员
$permission.getUserRoles(uid)获取用户的角色列表
$permission.getUserPermissions(uid)获取用户的合并权限列表
$permission.checkPermission(uid, permission)检查用户是否拥有指定权限
$permission.assignRole(uid, roleName)给用户分配角色
$permission.removeRole(uid, roleName)移除用户的角色
$permission.updateRolePermissions(roleName, { allow, deny })更新角色的 allow/deny 权限列表
$permission.withBasic(clientId, clientSecret)创建访问其他应用 Permission 服务的客户端,返回同样的方法集合
$permission.asUser(uid)以指定用户 UID 访问当前应用 Permission 服务;也可链在 withBasic() 返回的客户端后访问目标应用

云函数以应用身份执行,$data 和 $permission 拥有完整操作权限。如需控制访问,请在代码中检查 caller 信息。

示例:VIP 等级自动升级 ​

javascript
const { playerUid } = params

// 获取玩家消费记录
const record = await $data.get('purchase_records', playerUid).catch(() => null)
const totalSpent = record?.document?.data?.totalSpent || 0

// 根据消费金额判断 VIP 等级
let targetRole = null
if (totalSpent >= 10000) targetRole = 'vip_gold'
else if (totalSpent >= 5000) targetRole = 'vip_silver'
else if (totalSpent >= 1000) targetRole = 'vip_bronze'

if (!targetRole) return { upgraded: false }

// 检查是否已有该角色
const userRoles = await $permission.getUserRoles(playerUid)
const hasRole = userRoles.roles?.some(r => r.name === targetRole)
if (hasRole) return { upgraded: false, currentRole: targetRole }

// 移除旧 VIP 角色,分配新角色
const vipRoles = ['vip_bronze', 'vip_silver', 'vip_gold']
for (const old of vipRoles) {
  if (userRoles.roles?.some(r => r.name === old)) {
    await $permission.removeRole(playerUid, old)
  }
}
await $permission.assignRole(playerUid, targetRole)

console.log(`Player ${playerUid} upgraded to ${targetRole}`)
return { upgraded: true, newRole: targetRole }

示例:权限检查中间件 ​

javascript
// 客户端调用前先检查权限

const { action } = params
const permMap = {
  'delete_post': 'post.delete',
  'ban_user': 'user.ban',
  'edit_config': 'config.edit',
}

const requiredPerm = permMap[action]
if (!requiredPerm) return { allowed: false, reason: 'unknown_action' }

const result = await $permission.checkPermission(caller.uid, requiredPerm)
return { allowed: result.hasPermission, permission: requiredPerm }

$account — 账号只读查询 ​

$account 提供账号相关的只读查询,适合在云函数内把 RID / 邮箱解析成 UID,或读取用户最小身份信息。它不会返回密码、验证码、2FA 密钥等敏感字段。

方法说明
$account.getUser(uid)通过 UID 获取用户最小信息
$account.findUser({ uid, rid, email })通过 UID、RID 或邮箱查询用户;按传入字段精确匹配
$account.getAvatar(uid)获取用户头像的时效链接

云函数内调用 $account 不受“存储/权限服务是否开启”的限制。

getUser(uid) ​

javascript
const user = await $account.getUser(uid)

返回 null 或:

typescript
{
  uid: string
  rid: string
  email: string
  authState: number
}

findUser(query) ​

javascript
const byRid = await $account.findUser({ rid: params.rid })
const byEmail = await $account.findUser({ email: params.email })
const byUid = await $account.findUser({ uid: params.uid })

返回值与 getUser 一致。findUser 是精确匹配,不做模糊搜索。开发者可以在业务函数里允许用户输入 RID、UID 或邮箱,然后统一解析成 UID 后写入数据合集。

javascript
const input = String(params.user || '').trim()
const user = await $account.findUser({
  uid: input.includes('-') ? input : undefined,
  rid: input.includes('@') ? undefined : input,
  email: input.includes('@') ? input : undefined,
})

if (!user || user.authState !== 1) {
  return { error: 'user_not_found' }
}

await $data.set('player_profile', user.uid, { nickname: user.rid }, user.uid)
return { uid: user.uid, rid: user.rid }

getAvatar(uid) ​

返回的 URL 带签名且有时效(按小时对齐,最长 1 小时后过期),可直接交给客户端作为 <img src="..."> 使用。

getAvatar(uid) ​

javascript
const result = await $account.getAvatar(uid)

参数

参数类型必填说明
uidstring是目标用户的 uid

返回值

typescript
{
    uid: string         // 入参回显
    exists: boolean     // 用户是否存在且已设置自定义头像;false 表示返回的是默认头像
    url: string         // 带签名的绝对 URL(http(s)://...),时效最长 1 小时
    expiresAt: number   // 签名过期时间(Unix 秒,整点对齐)
}

示例:取当前调用者头像

javascript
if (!caller.uid) return { error: 'not_logged_in' }
const me = await $account.getAvatar(caller.uid)
return { avatar: me.url, expiresAt: me.expiresAt }

示例:按 uid 取任意用户头像

javascript
const target = await $account.getAvatar(params.uid)
return {
    avatar: target.url,
    exists: target.exists,
    expiresAt: target.expiresAt,
}

示例:批量获取(并发)

javascript
const uids = params.uids || []
const results = await Promise.all(uids.map(uid => $account.getAvatar(uid)))
return { avatars: results }

提示:expiresAt 是按整点对齐的签名过期时间,便于客户端/CDN 缓存。如需更长时效请在客户端临近过期时再次调用云函数刷新即可。

$storage — 文件存储桥接 ​

$storage 允许云函数直接操作 Storage API 的文件存储,无需手动处理认证、重放防护和 HTTP 调用。使用前需要在管理面板中为应用开启 Storage 服务。

所有方法返回 Promise,支持 await。

方法说明
$storage.getCapabilities() / getUsage()查询应用实际上限、分片能力、配额、预留用量和月度 URL 签发诊断;真实传输量不可用时明确返回 null
$storage.getUploadUrl(path, size, contentType?, options?)获取单次上传 URL 和一次性令牌;旧调用保持兼容
$storage.confirmUpload(path, token)凭令牌确认上传,记录元数据并更新配额
$storage.createMultipartUpload(path, options)创建可续传分片会话
$storage.getMultipartUploadUrls(path, uploadId, partNumbers)批量签发分片 PUT URL
$storage.listMultipartUploadParts(path, uploadId)查询 provider 已收到的分片
$storage.completeMultipartUpload(path, uploadId, options)校验分片并幂等完成
$storage.abortMultipartUpload(path, uploadId)中止未完成会话
$storage.listMultipartUploads(options?)列出分片会话
$storage.getDownloadUrl(path, options?)获取 GET 与 HEAD 下载 URL,支持响应覆盖
$storage.head(path)回读对象存储权威元数据
$storage.delete(path)删除文件
$storage.list(options?)列出文件(支持前缀、分页)
$storage.getMeta(path)获取文件元信息
$storage.withBasic(clientId, clientSecret)创建访问其他应用 Storage 服务的客户端,返回同样的方法集合
$storage.asUser(uid)以指定用户 UID 访问当前应用 Storage 服务;也可链在 withBasic() 返回的客户端后访问目标应用

方法参数:

方法参数类型必填说明
getUploadUrlpathstring✅文件路径,1~512 字符
sizenumber✅最终 PUT Body 的字节数;默认上限 1 GiB,以 getCapabilities() 为准
contentTypestring❌MIME 类型,默认 application/octet-stream
optionsobject❌sha256、metadata、allowOverwrite、expiresIn
confirmUploadpathstring✅文件路径(与上传时一致)
tokenstring✅getUploadUrl 返回的上传令牌
getDownloadUrlpathstring✅文件路径
optionsobject❌expiresIn、downloadName、contentType、cacheControl
deletepathstring✅文件路径
listoptions.prefixstring❌路径前缀过滤
options.limitnumber❌每页数量,默认 50,最大 200
options.pagenumber❌页码,默认 1
getMetapathstring✅文件路径

返回值:

getUploadUrl 返回 { url, token, expiresIn, fullPath, requestedSize, contentType, contentLengthBound, requiredHeaders }:

字段类型说明
urlstring预签名 PUT URL(默认 1 小时,以 expiresIn 为准)
tokenstring一次性上传令牌(64 位 hex),confirmUpload 时需传入
expiresInnumberURL 有效时间(秒)
fullPathstring存储桶中的完整路径
requestedSizenumber申请 URL 时提交的预计字节数
contentTypestring申请 URL 时采用的 MIME 类型
contentLengthBoundboolean签名是否绑定精确的 HTTP Body 长度
requiredHeadersobject新式绑定启用时必须原样发送的长度和 MIME 请求头

getDownloadUrl 返回 { url, headUrl, expiresIn, size, contentType, etag, sha256, sha256Verification, sha256VerifyStatus }。GET URL 支持 Range;HEAD 必须使用单独的 headUrl,因为 SigV4 绑定 HTTP Method。默认 R2 对象完成后台全量回读后,sha256Verification 会由 declared 升级为 platform_verified。

字段类型说明
urlstring预签名 GET URL(有效期 1 小时)
headUrlstring同期签发、仅用于 HEAD 的 URL;不能与 GET URL 互换
sha256VerifyStatusstringpending / processing / verified / failed
expiresInnumberURL 有效时间(秒)
sizenumber文件大小(字节)
contentTypestringMIME 类型

list 返回 { total, files },其中 files 数组每项包含 path、size、contentType、uploadedBy、createdAt、updatedAt。

getMeta 返回 { path, size, contentType, uploadedBy, createdAt, updatedAt }。

delete 返回 { status: 'ok' }。

⚠️ 注意:$storage.getUploadUrl() 返回预签名 URL 和一次性令牌,不会自动上传文件。新调用可传第四个参数 { bindContentLength: true } 绑定精确长度;旧调用省略时保持兼容。客户端必须原样使用 URL。PUT 成功后调用 $storage.confirmUpload(),平台会通过对象存储 HeadObject 回读真实大小和类型再更新数据库与配额;confirmUpload(path, token) 不传 size/contentType 时直接采用实际值,云函数与旧客户端无需担心声明值与实际字节数存在细微差异。100 MiB 以上建议使用 Multipart;完整流程见 Storage API。

示例:完整上传流程 ​

云函数接收客户端传来的文件内容,完成上传和确认:

javascript
const { fileName, fileData } = params
// fileData: base64 编码的文件内容

// 1. 先得到最终二进制 Body;byteLength 才是上传字节数
const binaryData = Uint8Array.from(atob(fileData), c => c.charCodeAt(0))

// 2. 获取上传 URL 和令牌
const uploadRes = await $storage.getUploadUrl(
  `uploads/${fileName}`,
  binaryData.byteLength,
  params.contentType,
  { bindContentLength: true },
)
const { url, token } = uploadRes

// 3. 将文件 PUT 到预签名 URL(云函数内 fetch 可访问公网)
await fetch(url, {
  method: 'PUT',
  headers: { 'Content-Type': params.contentType || 'application/octet-stream' },
  body: binaryData.buffer,
})

// 4. 凭令牌确认上传
const result = await $storage.confirmUpload(`uploads/${fileName}`, token)

return { uploaded: true, path: result.path, size: result.size }

示例:文件下载与分发 ​

javascript
// 获取下载链接分发给客户端
const { url, headUrl, expiresIn, size, contentType } = await $storage.getDownloadUrl(params.filePath)
return { downloadUrl: url, headUrl, expiresIn, size, contentType }

示例:列出用户相关文件 ​

javascript
// 列出某个前缀下的所有文件
const { total, files } = await $storage.list({
  prefix: `users/${caller.uid}/`,
  limit: 20,
  page: 1,
})

// files 包含: path, size, contentType, uploadedBy, createdAt, updatedAt
return {
  total,
  files: files.map(f => ({
    path: f.path,
    size: f.size,
    contentType: f.contentType,
    createdAt: f.createdAt,
  })),
}

示例:清理过期文件 ​

javascript
const { files } = await $storage.list({ prefix: 'temp/', limit: 200 })
const now = Date.now() / 1000
const expiredFiles = files.filter(f => f.createdAt && now - Number(f.createdAt) > 86400 * 7)

for (const file of expiredFiles) {
  await $storage.delete(file.path)
  console.log(`Deleted expired file: ${file.path}`)
}

return { cleaned: expiredFiles.length }

示例:用户头像存储 ​

javascript
// 接收客户端上传的头像,存入用户专属目录
if (!caller.uid) return { error: 'not_logged_in' }

const { imageData, contentType } = params
const path = `avatars/${caller.uid}/${Date.now()}.png`

const uploadRes = await $storage.getUploadUrl(path, params.imageSize, contentType)
const { url, token } = uploadRes
const bytes = Uint8Array.from(atob(imageData), c => c.charCodeAt(0))
await fetch(url, { method: 'PUT', body: bytes.buffer })
const result = await $storage.confirmUpload(path, token)

return { path: result.path, size: result.size }

$crypto — 加密解密 ​

$crypto 提供云函数内的加密专用能力。它使用 AES-256-GCM 做认证加密,适合在服务端加密需要落库或转交给客户端保存的敏感业务数据。

哈希、摘要、校验和不是加密。需要可逆保护数据时使用 $crypto.encrypt() / $crypto.decrypt();需要存储密码时应使用专门的密码哈希方案,而不是这里的对称加密。

除使用证书策略的 $crypto.jwsVerify() 外,$crypto 方法均为同步方法。策略验签可能执行 OCSP 查询,因此必须使用 await。

方法说明
$crypto.encrypt(plaintext, key, options?)加密字符串或 JSON 可序列化数据
$crypto.decrypt(encrypted, key, options?)解密 $crypto.encrypt() 返回的对象或 compact 字符串
$crypto.generateKey(encoding?)生成 32 字节随机密钥,默认返回 base64
$crypto.randomBytes(size?, encoding?)生成随机字节字符串,size 范围 1~1024,默认 32
$crypto.hash(algorithm, data, encoding?)计算摘要,支持 sha1、sha256、sha512、md5
$crypto.hmac(algorithm, key, data, encoding?)计算 HMAC,支持 sha1、sha256、sha512
$crypto.timingSafeEqual(a, b, encoding?)常量时间比较两个字符串或字节数组,适合比较签名
$crypto.jwsSign(options)使用 P-256 PKCS#8 私钥生成 ES256 Compact JWS;签名固定为 JOSE `R
$crypto.jwsVerify(options)使用公钥或应用级发布策略验证 ES256 JWS;策略路径同时验证 x5c 证书链、声明与吊销状态

hash / hmac 的输出编码支持 hex(默认)、base64、base64url。

ES256 签名示例:

javascript
const jwt = $crypto.jwsSign({
  algorithm: 'ES256',
  key: $secrets.ref('PARTNER_SIGNING_PRIVATE_KEY'),
  header: { kid: env.PARTNER_SIGNING_KEY_ID, typ: 'JWT' },
  payload: { iss: env.PARTNER_ISSUER_ID, aud: 'partner-api' },
})

需要验证 x5c 证书链时,云函数只能引用当前应用中已发布、固定版本的 JWS/X.509 策略。证书集合、证书位置约束、验证时间、声明规则和吊销模式由策略定义,运行时不会按 Provider 名称选择隐藏信任库。返回内容只含结构化 Header/Payload、策略摘要和证书摘要, 不含证书正文:

javascript
const verified = await $crypto.jwsVerify({
  signedPayload: params.signedPayload,
  algorithm: 'ES256',
  policy: $certificates.policy('partner_webhook_jws_v1', 1),
})

策略名和版本必须是部署时固定值,不能从 params、请求 Body、Header 或 Query 中读取。 旧 trustStore / expected / validateAt 参数已移除,不存在自动回退;原有云函数必须改用发布策略。 若只需要验证一个固定公钥,可继续传 key: $secrets.ref('PUBLIC_KEY'),但该路径不会验证 x5c 证书链、策略声明或吊销状态。

策略验签失败会抛出结构化错误:

typescript
{
  code: string
  retryable: boolean
  stage: string
  category: string
}

retryable=true 表示 OCSP 等外部依赖暂时不可用,Raw HTTP/Webhook 应返回 503 并允许 上游重试;签名、证书链、声明或配置错误属于永久失败,应返回 400。不要解析错误文案来 决定重试。OCSP 明确返回 revoked 或 unknown 时也属于永久拒绝。

options 参数:

字段类型说明
encodingstring密文、IV、认证标签输出编码:'base64'(默认)或 'hex'
keyEncodingstring密钥输入编码:'utf8'(默认)、'base64' 或 'hex'
aadstring附加认证数据,解密时必须传入相同值
parseJsonboolean仅 decrypt 使用;为 true 时额外返回 data: JSON.parse(plaintext)

返回值:

encrypt 返回:

typescript
{
  algorithm: 'aes-256-gcm'
  encoding: 'base64' | 'hex'
  iv: string
  tag: string
  ciphertext: string
  compact: string
}

compact 是 v1:<encoding>:<iv>:<tag>:<ciphertext> 形式,便于作为一个字符串保存。decrypt 可以直接接收该字符串,也可以接收完整对象。

示例:生成密钥 ​

javascript
const key = $crypto.generateKey('base64')

return {
  key,
  keyEncoding: 'base64',
  note: '将 key 保存到 env.CRYPTO_KEY,将 keyEncoding 保存到 env.CRYPTO_KEY_ENCODING',
}

新密钥应写入应用级 Secret Store 并通过 $secrets.ref() 使用。env.KEY 仅为既有函数保留兼容,不能把 Secret、JWT、完整 JWS 或证书写入日志。

示例:Webhook 签名校验 ​

显式设置 requestMode=raw 的 Raw HTTP 云函数会提供解析前捕获的 request.rawBody、 精确二进制 request.rawBodyBase64 和小写 key 的 request.headers,可直接用于第三方回调验签:

javascript
const authorization = String(request.headers?.authorization || '')
const signature = authorization.match(/^Signature\s+([a-f0-9]+)$/i)?.[1] || ''
const expected = $crypto.hash('sha1', request.rawBody + env.WEBHOOK_SECRET, 'hex')

if (!$crypto.timingSafeEqual(signature, expected)) {
  return { statusCode: 400, body: { error: 'invalid_signature' } }
}

return { statusCode: 204, body: '' }

使用 HMAC 的第三方平台可以这样写:

javascript
const signature = String(request.headers?.['x-signature'] || '')
const expected = $crypto.hmac('sha256', env.WEBHOOK_SECRET, request.rawBody, 'hex')

if (!$crypto.timingSafeEqual(signature, expected)) {
  return { statusCode: 401, body: { error: 'invalid_signature' } }
}

示例:加密并保存到 Data ​

javascript
const key = env.CRYPTO_KEY
const keyEncoding = env.CRYPTO_KEY_ENCODING || 'utf8'

const encrypted = $crypto.encrypt(params.secretProfile, key, {
  keyEncoding,
  aad: caller.uid,
})

await $data.set('secret_profiles', caller.uid, {
  encrypted: encrypted.compact,
  updatedAt: new Date().toISOString(),
}, caller.uid)

return { saved: true }

示例:读取并解密 ​

javascript
const key = env.CRYPTO_KEY
const keyEncoding = env.CRYPTO_KEY_ENCODING || 'utf8'

const doc = await $data.get('secret_profiles', caller.uid)
const decrypted = $crypto.decrypt(doc.document.data.encrypted, key, {
  keyEncoding,
  aad: caller.uid,
  parseJson: true,
})

return { profile: decrypted.data }

使用 aad 时,解密必须传入完全相同的 aad。密文、IV、认证标签或 aad 任意一项被篡改,解密都会失败。

$certificates — JWS/X.509 发布策略 ​

$certificates.policy(name, version) 返回不可伪造的策略引用,只能解析当前应用同名、同版本 且状态为 published 的策略。$certificates.hasPolicy(name, version) 可用于部署自检,但不能 替代固定版本引用。已发布的证书集合和策略不可原地修改;轮换证书或规则时应创建、发布新 版本,更新云函数中的版本字面量,验证后再弃用旧版本。

javascript
if (!$certificates.hasPolicy('partner_webhook_jws_v1', 1)) {
  throw new Error('Required certificate policy is not published')
}

const verified = await $crypto.jwsVerify({
  signedPayload: params.signedPayload,
  algorithm: 'ES256',
  policy: $certificates.policy('partner_webhook_jws_v1', 1),
})

运行时只加载源码中由 $certificates.policy('name', version) 直接固定引用的策略;不要把 名称或版本先放入变量、env 或请求字段再传入。这样外部输入无法选择或降低信任策略, 同时也不会把应用的其他证书集合无条件复制进隔离 Worker。

应用所有者通过 Cloud Function 管理 API 管理资源:

资源草稿与查询发布 / 弃用
证书集合GET/POST /cf/manage/{clientId}/certificate-bundles、GET/PUT/DELETE .../certificate-bundles/{bundleId}POST .../certificate-bundles/{bundleId}/publish、POST .../deprecate
JWS/X.509 策略GET/POST /cf/manage/{clientId}/jws-x509-policies、GET/PUT/DELETE .../jws-x509-policies/{policyId}POST .../jws-x509-policies/{policyId}/publish、POST .../deprecate

应用所有者和平台管理员可以写入;具备 Cloud Function 查看权限的协作者只能读取。每次 创建、修改、发布、弃用和删除都会进入应用操作日志;证书集合日志只记录 SHA-256 指纹, 不会保存完整 PEM。

创建证书集合时提交稳定名称和 1~32 个 PEM X.509 CA 证书;平台自动分配版本。发布证书 集合后,以返回对象中的 bundle.id 作为 certificateBundleId 创建策略草稿:

json
{
  "name": "partner_webhook_jws_v1",
  "certificateBundleId": "<published-bundle-uuid>",
  "definition": {
    "allowedAlgorithms": ["ES256"],
    "minChainLength": 2,
    "maxChainLength": 4,
    "leaf": { "ca": false, "keyTypes": ["ec"] },
    "validationTime": {
      "mode": "payloadClaim",
      "path": "signedAt",
      "unit": "milliseconds",
      "maxFutureSkewMs": 300000
    },
    "revocation": {
      "mode": "ocsp",
      "targets": "leaf",
      "unavailable": "retryableError"
    },
    "expectedClaims": [
      { "path": "audience", "equals": "my-application", "required": true }
    ]
  }
}

expectedClaims 支持严格类型匹配的 equals / oneOf、存在或缺失检查,以及 requiredWhen 条件规则。把应用身份、环境和允许的业务标识写入发布策略,不要由每次 请求传入这些信任边界条件。

$secrets — 只写 Secret 引用 ​

$secrets.ref(name) 返回不透明引用,供 $crypto.jwsSign、$crypto.jwsVerify 等受管能力 解析;它不会返回 Secret 字符串。has(name) 用于检查是否配置,names() 只返回名称。 应用级 Secret 同时合并进 env.NAME,确保旧函数迁移后继续运行。

管理 API 的 list/get/put 响应只含名称、isSet 和时间戳。空字符串会登记一个 isSet=false 的待配置 Key;此时 has() 返回 false,ref() 会报未配置。函数级脱敏 占位符仍保留现值,删除必须显式调用 DELETE /cf/manage/{clientId}/secrets/{NAME}。

控制台日志、错误和持久化执行记录会屏蔽 Secret;云函数返回值不做字符替换, 因为预签名 URL、签名、密文和不透明 token 必须逐字节保持。不要在返回值中显式暴露 Secret。

$appleIap — Apple IAP 服务端能力 ​

$appleIap 在通用 ES256/X.509 能力上封装 App Store Server API:

方法说明
createApiToken(options)创建 Apple 要求的 ES256 API JWT
getTransactionInfo(options)调用 Get Transaction Info,并使用调用方指定的发布策略验证交易 JWS
verifyNotification(options)兼容用薄封装;新函数应使用 $crypto.jwsVerify() 显式验证每一层 JWS

getTransactionInfo 的 environment: 'auto' 会先查询 Production;仅在 Apple 返回 4040010(交易不存在)时查询 Sandbox。GET 可配置每次请求超时和最多 2 次有限重试, 返回值始终带明确环境。

javascript
const result = await $appleIap.getTransactionInfo({
  transactionId: params.transactionId,
  issuerIdSecret: 'APPLE_IAP_ISSUER_ID',
  keyIdSecret: 'APPLE_IAP_KEY_ID',
  privateKeySecret: 'APPLE_IAP_PRIVATE_KEY_P8',
  bundleId: env.APPLE_IAP_BUNDLE_ID,
  policy: $certificates.policy('apple_iap_transaction_v1', 1),
  environment: 'auto',
  requestTimeoutMs: 8000,
  retries: 1,
})

模板优先使用 Secret 名称 APPLE_IAP_PRIVATE_KEY_P8,并兼容既有名称 APPLE_IAP_PRIVATE_KEY;privateKeySecret 本身可指定任意符合 Secret 命名规则的名称。

Notifications V2 不依赖 Provider 专用验签分支。外层通知、内嵌交易和内嵌续订必须分别 await $crypto.jwsVerify(),并直接引用部署时固定的发布策略:

javascript
const outer = await $crypto.jwsVerify({
  signedPayload: request.body.signedPayload,
  algorithm: 'ES256',
  policy: $certificates.policy('apple_iap_notification_v1', 1),
})

const data = outer.payload.data || {}
const transaction = data.signedTransactionInfo
  ? (await $crypto.jwsVerify({
      signedPayload: data.signedTransactionInfo,
      algorithm: 'ES256',
      policy: $certificates.policy('apple_iap_transaction_v1', 1),
    })).payload
  : null
const renewal = data.signedRenewalInfo
  ? (await $crypto.jwsVerify({
      signedPayload: data.signedRenewalInfo,
      algorithm: 'ES256',
      policy: $certificates.policy('apple_iap_renewal_v1', 1),
    })).payload
  : null

Production 通知必须配置正整数 APPLE_IAP_APP_APPLE_ID,并在验签后用严格数字类型匹配 data.appAppleId;Sandbox 可以不配置该值。Bundle ID、允许商品、环境和 App Apple ID 还应 作为静态 expectedClaims 固化到对应发布策略中。策略名和版本不能来自 env 或请求参数。 任一层出现 retryable=true 时 Webhook 返回 503,永久验签或声明失败返回 400;具体错误 响应写法见模板中心的“Apple IAP 服务端验单”。

模板中心的“Apple IAP 服务端验单”同时示范了 payment_events + $data.transaction/createOnce 的通知与验单幂等。业务发货/退款应放在同一事务内,审计只 保存 Provider、事件 ID、交易 ID、UID、商品、环境和状态,不保存完整 JWS。

$invoke — 函数互调 ​

$invoke 允许云函数同步调用同应用内的其他云函数,并获取返回值。通过 withBasic 可以调用其他应用的云函数;通过 asUser(uid) 可以让被调用函数以指定用户身份看到 caller.mode === 'user' 和对应 caller.uid。

如果某个函数只应作为内部 helper 使用,可以在函数设置中把 invokeAccess 设置为 cloud_function。此时该函数不会接受用户、匿名访问、Basic/Bearer HTTP 请求或定时任务, 只能由其他云函数通过 $invoke 调用。

javascript
const result = await $invoke(functionNameOrPath, params?, folder?)
参数类型说明
functionNameOrPathstring目标函数名称,或 folder/functionName 路径
paramsobject传递参数(可选)
folderstring目标函数的文件夹路径(可选);如果第一个参数已经是路径,可省略

返回值: 目标函数的返回值。

跨应用调用:

javascript
const invokePartner = $invoke.withBasic(env.PARTNER_APP_ID, env.PARTNER_APP_SECRET)
const result = await invokePartner.asUser(caller.uid)('get_exchange_rate', { from: 'gold', to: 'diamond' }, 'shared')
// 等价写法:
// const result = await invokePartner('shared/get_exchange_rate', { from: 'gold', to: 'diamond' })

限制:

  • 每次函数执行最多调用 10 次
  • 最大嵌套深度:5 层(函数 A → B → C → D → E)
  • 默认目标函数必须处于启用状态且在同一应用内
  • 跨应用调用时使用 $invoke.withBasic(clientId, clientSecret),目标应用必须审核通过并开通 Cloud Function 服务

示例:复用共享逻辑 ​

javascript
// 函数: create_order
const { items, buyerUid } = params

// 调用共享验证函数
const validation = await $invoke('validate_items', { items })
if (!validation.valid) return { error: validation.reason }

// 调用定价函数(位于 utils 文件夹)
const pricing = await $invoke('calculate_price', { items }, 'utils')
// 也可以写成:
// const pricing = await $invoke('utils/calculate_price', { items })

return { orderId: 'xxx', total: pricing.total }

示例:跨应用调用云函数 ​

调用其他应用的云函数时,可以使用 $invoke.withBasic() 传入目标应用的 Client ID 和 Client Secret。平台会按 Basic 认证校验目标应用凭据。

javascript
const targetClientId = env.PARTNER_APP_ID
const targetClientSecret = env.PARTNER_APP_SECRET
const invokePartner = $invoke.withBasic(targetClientId, targetClientSecret)

const result = await invokePartner('get_exchange_rate', {
  from: 'gold',
  to: 'diamond',
}, 'shared')
// 也可以写成 invokePartner('shared/get_exchange_rate', { from: 'gold', to: 'diamond' })

const rate = result?.rate || 1
return { exchangeRate: rate }

提示:仍可使用 fetch() 调用外部 Cloud Function API,但内置 $invoke.withBasic() 不需要手动拼接 Authorization、X-Timestamp 和 X-Nonce。

fetch — HTTP 请求 ​

云函数可以使用 fetch() 向公网发起 HTTP/HTTPS 请求。

限制:

  • 仅允许 http:// 和 https:// 协议
  • 禁止访问内网地址(localhost、127.0.0.1、10.*、192.168.*、172.16-31.*、IPv6 回环/链路本地/ULA 地址等)
  • timeoutMs 可配置 1~15 秒,默认 10 秒
  • 仅幂等 GET 支持 retries: 0~2 有限重试;其他方法不会自动重试
  • 响应体上限 1 MB

返回值:

字段类型说明
okboolean状态码是否为 2xx
statusnumberHTTP 状态码
textstring响应文本
jsonany自动解析的 JSON(解析失败为 undefined)
headersobject响应头
javascript
// 调用外部 API
const resp = await fetch('https://api.example.com/data', {
  timeoutMs: 8000,
  retries: 1,
})

if (!resp.ok) return { error: `API returned ${resp.status}` }
return resp.json

© 2026 StarEcho