ReboFiles文档
开始3
什么是 ReboFiles
快速开始
可运行的演示
集成到你的应用6
React 组件
Headless:你自己的界面
不使用框架
样式与主题
键盘与无障碍
语言
文件9
上传
预览与查看器
搜索
实时更改与大文件夹
评论
星标与字段
版本、回收站与空间
打包下载 .zip
Word 文档
服务器14
路由
框架与登录
谁可以做什么
存储:S3、GCS、Azure 与磁盘
检查上传
元数据:SQLite 与 Postgres
数据库结构
共享与链接
动态与通知
钩子、日志与限制
保留、法律保留与导出
桌面端与其他来源
同步文件夹
上线生产环境
协作1
AI 助手(MCP)
参考5
包
组件与 hooks
客户端 API
HTTP 路由
状态与路线图
↑↓ 移动↵ 打开Esc 关闭38 个页面

服务器 钩子、日志与限制

钩子、日志与限制

服务器对自身的报告,以及允许每个人多频繁地操作。每一项都是 createDriveHandler 的选项,你的代码出错只会被记录,不影响请求。
钩子

做了什么、哪里出错、耗时多少

import pino from 'pino';
import { metrics } from '@opentelemetry/api';
import { createDriveHandler, openTelemetry } from '@rebofiles/server';

const log = pino();

createDriveHandler({
  // …
  // Each thing done: a webhook, an audit of your own, another index, space counted.
  onChange: async ({ activity, entry }) => {
    await fetch(process.env.WEBHOOK!, { method: 'POST', body: JSON.stringify({ ...activity, size: entry?.kind === 'file' ? entry.versions[0].size : 0 }) });
  },
  logger: { error: (message, detail) => log.error({ detail }, message), warn: (message, detail) => log.warn({ detail }, message) },
  onRequest: openTelemetry(metrics.getMeter('rebofiles')),
});
  • onChange 会收到每一次操作,与动态记录一致(created、uploaded、version、renamed、moved、trashed、restored、deleted、shared、commented、held…),并附带操作后的条目,删除后为 null。
  • logger 记录服务器未在响应中说明的问题:未保存的动态、失败的钩子、无法检查的文件。默认为 console。
  • onRequest 会收到每个路由(如 GET /entries/:id)、状态、耗时和请求者。openTelemetry(meter) 将其记录为 http.server.request.duration 直方图和 5xx 计数器。
限制

每人可请求多少

类型按默认
uploads个人每分钟 1000 次
zip个人,以及链接每分钟 30 次
searchText个人每分钟 120 次
linkPassword链接和地址15 分钟内错误 10 次,之后连正确的也不接受
links链接每分钟 600 次
requests个人无
import { createDriveHandler } from '@rebofiles/server';

createDriveHandler({
  // …
  limits: {
    uploads: { max: 200, every: 60_000 },     // starting an upload, by person
    requests: { max: 1200, every: 60_000 },   // every request, by person
  },
  // Wrong passwords of a link counted by the address too, behind a proxy you trust.
  clientIp: (request) => request.headers.get('x-forwarded-for')?.split(',')[0].trim(),
});

超过限制时返回 429 rate-limited 并带有 Retry-After,网盘会用每个人的语言提示。设为 limits: false 则不限制。

HTTP

Webhook

通过 HTTP 把每次更改告知其他服务,就像 onChange 告诉你的代码一样:从不拖慢触发更改的请求。

import { everyChange, verifyWebhook, webhook } from '@rebofiles/server';

createDriveHandler({
  ...options,
  // Each change POSTed as JSON, signed, tried again three times: only these actions, or all.
  onChange: everyChange(
    webhook({ url: 'https://crm.example/hooks/files', secret: process.env.HOOK_SECRET!, actions: ['uploaded', 'version', 'deleted'] }),
    ({ activity }) => audit(activity),
  ),
});

// Where it arrives: the body as it came, checked before it is trusted.
const ok = verifyWebhook(process.env.HOOK_SECRET!, await request.text(), request.headers);
  • 请求体:id(每次重试相同)、action、at、by、简要的 entry 和 activity。请求头 X-Rebofiles-Event、X-Rebofiles-Delivery、X-Rebofiles-Timestamp 和 X-Rebofiles-Signature(时间戳和请求体的 HMAC-SHA256)。
  • 一秒后和四秒后重试,但因请求本身被拒绝(4xx)时不重试;未送达时会通知 onFailure。
Redis

多个进程

计数默认保存在进程内,除非你提供的 `limiter` 为所有进程保存:基于 Redis 只需几行。

limiter.ts
import { Redis } from 'ioredis';
import type { Limiter } from '@rebofiles/server';

const redis = new Redis(process.env.REDIS_URL!);

/** The same windows for every process: a count per key, gone when its window is. */
export const redisLimiter: Limiter = {
  async hit(key, limit, how) {
    if (how?.peek) {
      const [count, ttl] = await Promise.all([redis.get(key), redis.pttl(key)]);
      return Number(count) >= limit.max ? { ok: false, retryAfter: ttl } : { ok: true, retryAfter: 0 };
    }
    const count = await redis.incr(key);
    if (count === 1) await redis.pexpire(key, limit.every);
    return count <= limit.max ? { ok: true, retryAfter: 0 } : { ok: false, retryAfter: await redis.pttl(key) };
  },
};