文档导航通知与文件

接入文档

灵策中台接入指南:站内通知与文件直传

本页目录

接入灵策中台的产品(后台登记的「中台应用」)除了登录(统一登录)和收款(收款与 Webhook),还可以用两项公共能力:

  • 站内通知:给登录过你的应用的灵策用户发一条站内信,用户在灵策网页版 / App 的消息中心看到,同时进邮件摘要;
  • 文件直传:拿一个签名上传地址,浏览器或服务端直接把文件 PUT 到灵策的对象存储,不经过你的服务器带宽。

两项都只在你的服务端调用,鉴权和收款相同(client_id + client_secret,见 收款与 Webhook 第 2 节)。全部错误码见 错误码。

#1. 开通

找灵策运营(超级管理员)在后台「灵策中台 → 应用 → 能力」里打开「开通通知」「开通文件直传」。两项默认都关,没开时调用返回 403 center_notify_disabled / 403 center_files_disabled。开通前需要先生成应用密钥。

#2. 站内通知 POST /api/center/notify/messages

JSON
{
  "userId": "cmvsym1j60002eu4x2y8qchxq",
  "title": "订单已发货",
  "body": "你在演示应用的订单 A-1001 已发货,预计明天送达。",
  "eventKey": "order-shipped:A-1001"
}
字段 说明
userId 收件人的灵策账号 sub(用户登录你的应用时,id_token / userinfo 里的 sub)。只能发给授权过你的应用的用户:用户在你的应用完成过一次登录(换令牌)才算
title ≤ 60 字。中台会自动加上「【你的应用名】」前缀,用户能分辨来源,也防止冒充平台公告
body ≤ 2000 字,纯文本。不支持外链跳转
eventKey 幂等键,1-100 位字母、数字或 _ . : -,在你的应用内唯一。网络错误时用同一个 eventKey 重试,不会重复发

返回:

JSON
{ "code": 0, "data": { "messageId": "…", "idempotent": false } }

idempotent = true 表示这个 eventKey 之前已经发过,本次没有新发。

错误:

状态 code 说明
403 user_not_authorized 这个用户没有授权过你的应用(从没登录过)。未知的 userId 也是这个错误,不会告诉你账号是否存在
404 user_not_found 授权过,但账号已停用或注销
409 event_key_conflict 同一 eventKey 之前发给了另一个用户
429 user_rate_limited 这个用户 24 小时内收到你的应用的消息已达上限(默认 20 条)
429 rate_limited 你的应用整体发送过快

请只发和用户本人相关的通知(订单、到账、审核结果等),不要群发营销内容。

#3. 文件直传 POST /api/center/files/presign

JSON
{ "contentType": "image/png", "sizeBytes": 204800, "visibility": "public" }

visibility:public(默认,公共读)或 private(私有,见 3.1)。

返回:

JSON
{
  "code": 0,
  "data": {
    "key": "center/demo/202610/3f0c…e1.png",
    "uploadUrl": "https://…(签名地址,10 分钟有效)",
    "method": "PUT",
    "headers": { "Content-Type": "image/png", "x-oss-forbid-overwrite": "true" },
    "publicUrl": "https://cdn.lynxce.cn/center/demo/202610/3f0c…e1.png",
    "maxBytes": 20971520,
    "expiresInSeconds": 600
  }
}

然后用 uploadUrl 直接上传,headers 必须原样带上(它们参与签名)。从浏览器直传时,对象存储要允许你的域名跨域 PUT:找灵策运营把你的站点域名加进存储桶的 CORS 规则(允许方法 PUT、允许头 Content-Type、x-oss-forbid-overwrite),否则浏览器会报跨域错误;服务端直传不受影响。

TypeScript
await fetch(upload.uploadUrl, { method: upload.method, headers: upload.headers, body: file });
  • 路径由中台生成,按应用隔离,不能自己指定;同一地址只能上传一次,不能覆盖;

  • 支持的类型和单文件上限:

    类型 上限
    image/png、image/jpeg、image/webp、image/gif 20MB
    application/pdf 50MB
    text/plain、text/csv 10MB
    audio/mpeg 100MB
    video/mp4 500MB
  • 注意public 文件的 publicUrl 任何人都能访问。身份证、合同、聊天记录这类隐私文件请用 private;

  • 不支持的类型 / 超出上限返回 400 file_type_not_allowed / 400 file_too_large;存储未配置返回 503 storage_unavailable。

#3.1 私有文件

visibility: "private" 的文件放在私有存储,返回里 publicUrl 为 null。要读取时,在服务端换一个短时下载地址:

POST /api/center/files/download-url

JSON
{ "key": "center/demo/202610/3f0c…e1.pdf", "expiresInSeconds": 600, "downloadFileName": "合同.pdf" }

返回 { "downloadUrl": "…", "expiresAt": "…" }。expiresInSeconds 取 60-3600,默认 600。只能换你的应用自己上传的 key,别的 key 一律 404 file_not_found。下载地址谁拿到都能在有效期内下载,请只在用户确有权限时现换现给,不要长期保存。

#4. SDK

TypeScript
import { CenterServerClient } from "@ai-employee/center-sdk";

const center = new CenterServerClient({
  baseUrl: "https://center.lynxce.cn",
  clientId: process.env.LYNXCE_CLIENT_ID!,
  clientSecret: process.env.LYNXCE_CLIENT_SECRET!,
});

await center.sendNotification({ userId: sub, title: "订单已发货", body: "…", eventKey: `order-shipped:${orderId}` });

const upload = await center.presignFileUpload({ contentType: file.type, sizeBytes: file.size });
// 把 upload 交给浏览器,由浏览器直传:fetch(upload.uploadUrl, { method: "PUT", headers: upload.headers, body: file })

const secret = await center.presignFileUpload({ contentType: "application/pdf", sizeBytes: file.size, visibility: "private" });
// …上传后,用户要看时:
const { downloadUrl } = await center.getFileDownloadUrl({ key: secret.key, expiresInSeconds: 300 });