# 使用 Func 构建站点后端能力｜Creght AI 编程指南

> Creght Func 完整指南，涵盖后端函数、JSON 表、鉴权、密钥、第三方 API、支付、文件上传、超时设置、原生 Fetch SSE 流式响应与 SSR 边界。

[![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)API for AI](/)

[查看 llms.txt](/llms.txt)

概览

- [Creght AI 编程指南](/api.md)

AI 可发现性

- [如何优化 llms.txt](/api/optimize-llms-txt.md)

站点配置

- [实现基于域名的多语言路由](/api/domain-locale-routing.md)

后端

- [使用 Func 构建站点后端能力](/api/func-backend.md)

本页目录

- [适用范围](#when-to-use)
- [适合 Func](#适合-func)
- [优先用现成能力](#优先用现成能力)
- [不适合 Func](#不适合-func)
- [项目隔离](#项目隔离)
- [文件、键与方法](#files-and-methods)
- [运行时与代码规则](#runtime-rules)
- [ctx 能力速查](#context)
- [数据](#数据)
- [鉴权](#鉴权)
- [缓存](#缓存)
- [请求与响应](#请求与响应)
- [Cookie](#cookie)
- [资源](#资源)
- [流式事件](#流式事件)
- [诊断](#诊断)
- [返回值与错误](#responses)
- [JSON 表与持久化](#tables)
- [鉴权、密钥与第三方服务](#auth-secrets-external)
- [资源上传](#assets)
- [超时配置与诊断](#timeout)
- [原生 Fetch + SSE 流式响应](#streaming)
- [浏览器调用与 SSR 边界](#browser-ssr)
- [开发与验收流程](#workflow)

后端/使用 Func 构建站点后端能力

# 使用 Func 构建站点后端能力

面向 AI 编程智能体的 Func 完整实现规范：涵盖文件与方法、运行时、数据、鉴权、密钥、第三方服务、支付、资源、超时、原生 SSE 与 SSR 边界。

复制 Markdown 链接

Func 是 Creght / Talizen 的项目级后端运行时，用于把必须在服务端完成的小型业务流程放进站点项目：受保护的数据写入、预约与候补、用户资料更新、第三方 API、Webhook、支付对接，以及需要密钥或流式输出的 AI 请求。

**智能体目标**

把服务端代码放在 `/backend/func`，通过 `ctx` 使用平台能力，从页面调用稳定的函数键；不得把项目 ID、密钥、身份逻辑或持久化写入泄漏到浏览器。

## 适用范围

### 适合 Func

预约、候补、RSVP、线索分发、资料更新、可用性检查、登录后操作、第三方 API、Webhook、支付和简单 JSON 数据读写。

### 优先用现成能力

普通内容展示使用 CMS；静态联系表单使用 `talizen/form`；登录界面与会话状态使用 `talizen/auth`。

### 不适合 Func

脱离请求继续执行的后台任务、重型文件处理、无限流、定时器轮询、自建账号/会话系统、OAuth 回调或令牌交换。

### 项目隔离

Func 天然属于当前项目。输入、代码和分支逻辑中都不应出现 `project_id`、 `site_id` 或内部表 ID。

## 文件、键与方法

Func 文件位于 `/backend/func`。文件的无扩展名路径就是函数键： `/backend/func/booking.ts` 对应 `booking`， `/backend/func/profile/settings.ts` 对应 `profile/settings`。

点号保留给导出方法。单一操作可导出 `main`；相关操作应从同一文件直接导出多个方法，不要自行编写分发器。

```typescript
// /backend/func/booking.ts
import type { TalizenFuncContext } from 'talizen/func-runtime'

export function create(input, ctx: TalizenFuncContext) {
  if (!input?.startAt) throw new Error('startAt is required')
  const user = ctx.auth.requireUser()
  return ctx.db.insert('appointments', {
    startAt: input.startAt,
    userId: user.id,
  })
}

export function availability(input, ctx: TalizenFuncContext) {
  return ctx.db.query('appointments', { date: input.date })
}
```

```typescript
invoke('booking.create', input)       // key booking, method create
invoke('profile/settings.update', input)
invoke('booking', input)              // key booking, method main
```

## 运行时与代码规则

- 使用 ESM 导出： `export function method(input, ctx)`；只在需要时从 `talizen/func-runtime` 导入 TypeScript 类型。
- 所有平台能力都从 `ctx` 获取；不要使用旧式全局变量 `data`、 `db`、 `auth` 或 `cache`。
- 在 Func 内验证、裁剪和规范化全部输入。预期业务状态返回结构化 JSON；无效请求或意外故障才抛出错误。
- Func 不是完整 Node.js 运行时。不要依赖 Node 内置模块；计时器 `setTimeout`/ `setInterval` 不受支持，不能用于等待、轮询或重试。
- 标准 `fetch`、 `Response`、 `TextDecoder` 与 Web Crypto 可用于第三方 HTTP、响应读取和签名校验。

## ctx 能力速查

### 数据

`ctx.db.get/query/insert/update/delete` 操作项目 JSON 表；所有写入受表的 JSON Schema 校验。

### 鉴权

`ctx.auth.currentUser()` 读取当前用户； `ctx.auth.requireUser()` 在未登录时直接拒绝。

### 缓存

`ctx.cache.get/set/del/incr/expire` 适合短期结果、计数器与过期状态，不能替代持久化表。

### 请求与响应

`ctx.request.host/ip/method/path` 提供请求信息；原始请求体可按 Fetch 语义读取。使用 `ctx.response.status(code)` 设置状态码。

### Cookie

通过 `ctx.cookies` 读取、设置或删除 Cookie。SSE 第一个事件发送后响应头已提交，不能再修改 Cookie。

### 资源

`ctx.assets.upload({ filename, mimeType, base64 })` 上传运行时文件，并返回可持久化的 URL、路径和大小元数据。

### 流式事件

`ctx.sse.send(event, data)` 发送有界 SSE 事件；平台负责最终 `done` 或 `error` 事件。

### 诊断

使用 `console.log/warn/error` 输出日志，并使用 `ctx.trace_id` 关联一次调用，避免记录密钥与敏感正文。

## 返回值与错误

Func HTTP 层返回 `{ "result": ... }` 或 `{ "error": "..." }`。浏览器里的 `invoke()` 会解包成功的 `result`，失败时抛出 `TalizenFuncError`。业务上可预期的“无库存”“重复提交”等状态建议作为明确对象返回。

```typescript
import { invoke, TalizenFuncError } from 'talizen/func'

try {
  const booking = await invoke('booking.create', input)
} catch (error) {
  const message = error instanceof TalizenFuncError
    ? error.message
    : '提交失败，请稍后重试'
}
```

## JSON 表与持久化

写入前必须存在表定义。表文件位于 `/platform/table/<key>.json`，文件名就是表键；使用普通文件工具创建或修改，不存在单独的表结构工具。

```json
{
  "name": "Appointments",
  "desc": "Booked appointment slots",
  "json_schema": {
    "type": "object",
    "properties": {
      "startAt": { "type": "string" },
      "userId": { "type": "string" },
      "status": { "type": "string" }
    },
    "required": ["startAt", "userId"]
  }
}
```

Schema 必须是具有非空 `properties` 的对象， `required` 只能引用已声明字段，业务字段不能叫 `key`。写入仅允许声明过的字段。表不可直接改名；仍有记录时不可删除。记录管理使用 `list_table_records`、 `create_table_record` 等记录工具。

> 用户数据使用平台 `user.id` 作为归属键，不要以邮箱充当身份主键，也不要创建 `users`、 `auth_users` 等账号身份表。

## 鉴权、密钥与第三方服务

页面的登录 UI 使用 `useAuth()`；Func 只负责保护后端动作。密钥通过 `process.env.NAME` 读取，由用户在 Creght「Backend / Env」面板 `panel/backend/env` 配置。智能体不得声称已代管环境变量，也不得把密钥写入配置、代码、组件、示例、注释或生成结果。

```typescript
export async function main(input, ctx) {
  ctx.auth.requireUser()
  const response = await fetch('https://api.example.com/v1/generate', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.EXAMPLE_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(input),
  })
  if (!response.ok) {
    ctx.response.status(502)
    throw new Error('Upstream request failed')
  }
  return response.json()
}
```

接收 Webhook 时，按供应商规范读取原始正文并使用 Web Crypto 校验签名，再解析 JSON；不要先重写正文。支付也属于自定义服务端 Func：密钥放环境变量，验证回调签名与幂等键，返回明确订单状态。平台没有内置支付 SDK。

## 资源上传

```typescript
const asset = await ctx.assets.upload({
  filename: 'report.pdf',
  mimeType: 'application/pdf',
  base64: input.base64,
})
await ctx.db.insert('reports', {
  userId: ctx.auth.requireUser().id,
  url: asset.url,
  path: asset.path,
  size: asset.size,
})
```

大文件应上传后只保存 URL、路径和大小等元数据，不要把大段 base64 存入表或作为 Func JSON 结果返回。

## 超时配置与诊断

`invoke` 默认超时是 **5 秒**，Func runner 的默认最大执行时间是 **300 秒**。有界的模型生成或第三方请求可以在调用端设置更长时间；这不意味着可以启动脱离请求继续执行的后台任务。

```typescript
const result = await invoke('image.generate', input, {
  timeoutMs: 120000,
})
```

遇到 `context deadline exceeded` 或 `context timeout` 时：

1. 先检查页面实际传给 `invoke(..., { timeoutMs })` 的值。
2. 用相同输入、相同模型提高超时重试。 `run_func.timeout_ms` 只影响这次自测，不能证明平台存在同样的硬限制。
3. 如果提高后成功，就提高生产调用端的 `timeoutMs` 并验证真实路径。

> 不要把缩短输出、降低 `max_tokens`、切换模型/供应商、创建新表或伪造后台任务当成超时修复。

## 原生 Fetch + SSE 流式响应

普通 `invoke()` 等待一个完整 JSON 结果。需要有界增量输出时，服务端通过 `ctx.sse.send` 发事件，浏览器直接使用原生 `fetch` 和 `ReadableStream`；不需要 `invokeStream` 封装。

```typescript
// /backend/func/writer.ts
export async function main(input, ctx) {
  ctx.sse.send('token', { text: 'Hello' })
  ctx.sse.send('token', { text: ' world' })
  return { ok: true }
}
```

```typescript
const response = await fetch('/func/writer?stream=1&timeout_ms=120000', {
  method: 'POST',
  headers: {
    Accept: 'text/event-stream',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(input),
})
if (!response.ok || !response.body) throw new Error('Func stream failed')

const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })

  const frames = buffer.split(/\r?\n\r?\n/)
  buffer = frames.pop() || ''
  for (const frame of frames) {
    const event = frame.match(/^event:\s*(.+)$/m)?.[1]
    const data = frame.match(/^data:\s*(.+)$/m)?.[1]
    if (event === 'token' && data) {
      output += JSON.parse(data).text
    }
  }
}
```

`reader.read()` 返回的是任意字节块，不保证一个块就是一个事件，必须跨读取缓存，并且只按空行分隔 SSE frame。平台最终发送 `done` 或 `error`；调用超时仍然生效。第一个事件发出后不能再更改 Cookie。

## 浏览器调用与 SSR 边界

写入操作应从浏览器事件处理器调用 Func，持久化状态留在 Func/JSON 表，而不是只放 React state。公开 HTTP 路径使用 `/func/<key>`；不要在页面路由中占用 `/func/*`。

不要在 `getServerSideProps` 中调用 Func。SSR 只提供适合首屏公共数据的请求/Cookie 辅助能力，刻意不暴露 `ctx.auth`、 `ctx.func`、 `ctx.db` 或 `ctx.cache`。鉴权、私有数据、写入和缓存/数据库逻辑应留在 Func 与浏览器交互流程。

## 开发与验收流程

1. 确认 CMS 或 `talizen/form` 不能满足需求。
2. 创建或核对 `/platform/table` 下需要的 JSON 表。
3. 在 `/backend/func` 编写 ESM 导出，验证输入并从 `process.env` 读取密钥。
4. 页面使用 `invoke('key.method', input)`；仅流式场景使用原生 Fetch/SSE。
5. 使用 `run_func` 或 Creght CLI 的 Func 运行命令传入样例做后端自测；记住测试超时只作用于本次运行。
6. 修改页面或组件后运行 lint，并在真实页面验证成功、业务失败、未登录、第三方失败、超时和流式结束路径。

- 没有项目/站点/内部表 ID 进入浏览器 payload。
- 没有硬编码密钥、旧式全局变量、手动方法分发或计时器。
- 表已存在，Schema 与实际写入字段一致。
- 受保护操作调用 `requireUser()`，记录使用 `user.id`。
- 大文件走资源上传，第三方/Webhook/支付校验错误与签名。
- 普通调用返回结构化 JSON；SSE 正确缓冲 frame，并处理 `done`/ `error`。
- 调用端超时与实际任务时长匹配，生产路径已经验证。

**完成标准**

一个合格的 Func 应具有清晰稳定的函数键、最小必要权限、可验证输入、可预测返回值、受 Schema 约束的持久化，以及可在页面真实调用路径中复现的错误和超时行为。

![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)

此网站使用 [Creght](/) 创建

![微信客服](https://fsu.creght.com/site/2066727200882692096/1785119134612__image.png)

微信客服

## 链接

- [价格](/price.md)
- [解决方案](/solution.md)
- [客户案例](/customers.md)
- [帮助中心](/help.md)
- [联系我们](/contact.md)
- [博客](/blogs.md)
- [退款说明](/tuikuan.md)

## 资源

- [全部资源](/resources.md)
- [模板](/templates.md)
- [组件库](https://creghtlib.site.creght.com)
- [动效库](/effects.md)
- [Figma to Creght](/figma2creght.md)
- [AI 编程指南](/api.md)

## 产品对比

- [对比上线了](/creght-vs-sxl.md)
- [对比凡科建站](/creght-vs-fkw.md)
- [自己写代码 vs Creght](/compare/self-coding.md)
- [外包 vs 自己做](/compare/outsourcing.md)

## 协议

- [用户协议](/legal/terms.md)
- [隐私政策](/legal/privacy.md)
- [可接受使用政策](/legal/acceptable-use.md)

## 社交媒体

- [小红书](https://www.xiaohongshu.com/user/profile/5a38606811be10715f4895b6)
- [哔哩哔哩](https://space.bilibili.com/513308095)

[蜀ICP备2023038192号-2](https://beian.miit.gov.cn)
