跳转到正文

Read this guide in English

任务生命周期 ​

当前状态 ​

I4 本地任务闭环已验收。任务记录、幂等提交、待投递恢复、执行租约、取消与有限重试已实现,并通过实际 PostgreSQL 并发验收。业务队列消费者、结果发布和定时恢复已接入同一生产 Worker 入口,真正 workerd + 本地 Queue / R2 + 临时 PostgreSQL 的接口、下载与重启验收通过。个人任务 API、列表与详情工作台已接入主预览,浏览器实际完成文本任务、结果预览 / 下载和刷新恢复。死信消费 / 账号恢复、执行预算耗尽及两份干净新站的任务闭环已通过本地验收。官网仍区分本地能力与云端交付;上述验收不代表已部署云端。

现有 JSON / 文本客户端工具继续同步运行且免费。首个队列处理器将采用确定性的 UTF-8 文本规范化,不调用外部模型,不收积分。I5 已接入可配置报价与事务预扣 / 结算,见收费教程;外部供应商对账归实际供应商接入时验证。

数据与服务边界 ​

I5 增量迁移 0007_task_charges.sql 保存任务价格 / 收费状态与账本任务关联,新代码运行前需应用。

增量迁移 0004_task_lifecycle.sql 增加三个表,0005_task_results.sql 增加执行结果引用与允许零字节结果的文件约束,保留此前迁移与已有数据。更新后的文件删除 / 清理、队列与定时维护会查询这些结构,所以部署新代码前必须依次应用迁移;不要只更新 Worker。自动验收仅迁移脚本创建的临时数据库;本轮另更新已授权的任务自有 loopback 主预览库并重启预览,原三份验收文件保留,真实浏览器任务另新增一份结果。生成站与远程数据库本轮未迁移。

表用途
task个人所有者、请求键和指纹、处理器 / 版本、输入快照、参数、状态、次数、执行 / 投递租约、结果引用与稳定错误码
task_attempt每次领取版本的执行记录与该次预留结果 ID;task_id + lease_version 唯一
task_input_pin活跃任务保留的输入文件;终态释放,历史任务仍保留输入 ID / 名称 / 摘要

历史输入 / 结果 ID 没有直接文件外键,避免文件到期删除顺带丢失任务记录。活跃引用有文件外键,账号删除仍遵守当前个人数据级联规则;任务记录不是财务审计账本。

代码按实际职责分开:api/lib/tasks/store.ts 负责用户请求与所有权;delivery.ts 负责投递;lifecycle.ts 负责领取、失败与失联恢复;consumer.ts 负责确定性处理与消息结算;results.ts 负责结果预留、发布与补偿。首版只接受个人账号和一个现有 text/plain 文件,参数严格校验,固定处理器 text-normalize 版本 1;没有任意脚本或 URL 执行器。

幂等与并发 ​

user_id + request_key 唯一。请求键为客户端生成的 UUID,同一次提交重试沿用原键;同键与同输入返回已有任务,同键换输入 / 参数报冲突。终态和原文件删除后,同一请求仍返回原任务,不重新执行。

每账号默认最多五个活跃任务,由 tasks.maxActive 控制。创建与手动重试锁定账号行,再核对活跃计数;次数和文件输入不能通过并发请求绕过。单账号事务串行的实际吞吐 / 云端负载还未测试,后续依据负载证据调整,不先添加另一套限流存储。

创建任务和保留输入在同一数据库事务内。提交与删除都先锁定同一文件行,再检查最新状态或引用。删除先赢时文件转为 deleting,提交拒绝;提交先赢时引用提交,删除返回 FILE_IN_USE。过期清理跳过引用中的文件,终态释放后再正常清理。保留输入不延长用户下载权限;过期文件仍不能由用户直接下载。

投递与领取 ​

队列只发送 { taskId, dispatchVersion },不携带文件内容或报价。任务先持久化为 pending,投递采用 30 秒的独立租约;发送失败保留记录,30 秒后可补投。成功投递后记录 queued 与时间;两分钟仍未领取的队列记录可以再次投递。维护单批最多检查 20 条。

迟到发送端只更新仍持有自己投递 token 的记录。消费者领取会清除该 token;取消也会清除未执行任务的 token。因此消息已开始执行甚至到达终态后,旧发送端不能把它覆盖成 queued。发送失败与业务执行失败分别记录,失败投递不消耗执行次数。

Cloudflare Queues 默认为至少投递一次,可能重复,结果去重由任务服务承担。官方投递保证(核对:2026-10-10)。tasks:validate 已通过实际本地 Queue 投递给真正业务 Worker;tasks:validate-store 的受控发送夹具仍仅用于数据库竞态。两者都不证明云端运行。

领取原子更新状态、执行次数与递增版本,并在同一事务插入尝试记录。两分钟执行租约到期后旧版本不得写回,重复消息不能再领取 running 或终态任务。数据库约束要求只有 running 有执行租约,只有 succeeded 有结果引用。

重试与取消 ​

单任务总执行预算默认三次,由 tasks.maxAttempts 在创建时保存;改配置不增加已有任务的预算。自动和手动尝试共用,不因刷新或手动重试清零。只有已分类的 STORAGE_UNAVAILABLE 自动重试,按尝试次数延迟 30 / 60 秒;输入缺失、处理器不可用、存储配额不足或结果过大先失败。手动重试仅允许明确的存储、租约或处理器故障,包含腾出空间后的 STORAGE_FULL,重新核对输入有效期 / 摘要、活跃名额和剩余次数。

pending、queued、retry_wait 的取消立即终止并释放输入;running 的取消先记录请求,保留输入直到执行器结算或租约恢复。终态重复取消返回原状态,不重新释放。收费任务的取消与返还在同一事务结算;可重试失败仍保留预扣,本人可放弃并返还。

失联恢复只自动重试已知的确定性文本处理器。未知处理器进入 reconciling,保留引用并停止自动投递,取消也仅记录请求。这个状态边界不等于已实现供应商查询 / 对账;必须等真实外部服务接入后按其能力交付。

执行与结果发布 ​

文本处理器读取活跃引用中的输入,核对所有者、文件状态、大小、元数据摘要和实际字节的 SHA-256,严格解码 UTF-8。它把 CRLF / CR 换行改为 LF,按参数移除行末空格 / 制表符,不添加额外换行。已经受理并保留的输入到期后仍可完成任务,但用户下载权限不延长。

输出复用文件模块的配额预留、私有对象路径、到期策略和下载权限。先在当前租约的事务中预留 uploading 文件,关联该次尝试;该结果不出现在文件列表,也不可下载。对象写入成功后,在同一数据库事务标记文件 ready 与任务 succeeded,结束尝试并释放输入引用。空白文本可转换成零字节结果并正常下载;上传入口仍拒绝空文件。

发布检查当前执行版本、两分钟租约、该次结果引用、所有者、文件状态与有效期。发布时发现取消请求则结算 canceled 并丢弃输出;旧执行器不能覆盖新结果。对象写入失败只补偿自己的未发布文件,不删除 ready 结果:数据库提交成功但响应丢失时,消息会重试,已提交的结果必须保留。

读取输入对象、读取对象字节和写入结果对象分别有 15 秒等待上限。R2 API 没有这里可用的中止接口,超时不保证底层写入立即停止;迟到写入不能通过过期版本发布,若落成无记录对象,沿用文件模块带一小时宽限的孤立对象清理。删除失败保留文件记录和配额,待定时维护重试。此处没有承诺立即清除迟到对象,也没有新增第二套配额。

定时维护依次恢复过期执行、返还输入失效的失败任务预扣、清理非运行尝试的未发布结果、清理文件、补投到期任务;每批有上限。本地不会自动触发 Cron,验收显式调用真正 Worker 的 scheduled 入口。已分类业务失败按数据库状态重试;未知基础设施错误要求消息延迟十秒重投,原始供应商响应不写入任务记录 / 日志。

死信与恢复 ​

业务队列最多追加三次基础设施重投,耗尽后由独立死信消费者处理。TASK_DEAD_QUEUE_NAME 必须与 Wrangler 的死信消费者名字一致,本地运行时从配置派生;生成新站时,目标队列、死信消费者与该变量一起重新命名。官方要求为死信配置独立消费者,见死信队列文档(核对:2026-10-10)。

迁移 0006_task_dead_letters.sql 增加投递版本与 task_dead_letter 记录。每次投递、手动恢复或结束旧租约都会推进版本;消费者只领取与数据库当前版本一致的消息。旧消息不能领取新一轮执行,迟到的死信也不能终止新投递、覆盖成功结果或撤销取消。

死信事务先锁定任务,再以 queue_name + message_id 唯一保存收据与状态转换,提交成功才确认消息。数据库暂时不可用或提交响应丢失会重投;唯一约束避免重复收据 / 重复转换。记录只含队列 / 消息标识、已核实任务引用、投递版本、时间和分类,不保存原始消息体、输入、验证码或供应商错误。分类为 applied(已停止等待任务)、deferred(保留运行租约)、stale(旧版本 / 终态)、missing、invalid;无对应任务的消息不保存其原始任务编号。

当前等待任务进入 failed / DELIVERY_EXHAUSTED,释放输入保留,停止自动补投。运行任务仍保留租约,可发布真实完成的结果;后续已知失败或租约过期才停止自动重试,未知处理器仍进入 reconciling。详情只向任务本人返回历史故障时间,恢复后保留该记录,不向浏览器返回队列名、消息编号或内部版本。管理员集中诊断界面归 I9,当前维护者可按任务编号核对 task_dead_letter 与 task_attempt。

恢复使用既有 tasks.retry 和详情重试按钮,继续要求个人所有权、本站来源、频率限制及已启用的验证码;检查处理器版本、当前名额、原输入状态 / 摘要 / 到期及剩余预算。恢复不清零 attemptCount,也不清空死信历史。未领取的基础设施投递失败不计处理次数;已经耗尽处理次数的任务拒绝继续执行,需要本人明确提交新任务,不由后台偷偷重置预算。

死信消费者自身设置十次重投。数据库持续不可用时,有限重投不能承诺无限保存消息;上线前需接入队列失败告警与运维恢复,归 I15。生产资源、部署、保留期限和真实容量仍待环境验收。

本地验收 ​

在主预览中操作 ​

  1. 登录后打开 /files,保存一个 UTF-8 文本文件。
  2. 打开 /tasks,选择已保存、未到期的文本文件;选择是否去掉行末空白,点击开始任务。
  3. 提交成功自动进入 /tasks/<任务 ID>。等待、执行、重试等待和终态来自服务器;首版没有伪造进度百分比。
  4. 仅成功后展示结果文件,可预览和下载。原文件保持不变,结果使用个人文件配额与到期策略。
  5. 等待 / 运行中的任务可请求取消;运行中需等执行器结算。符合条件且仍有预算时才显示手动重试,次数不会清零。
  6. 刷新、离开后返回仍能看到任务。断线会提示重连并禁止按旧状态操作;恢复联网或回到页面重新核对,活跃列表每五秒、详情每三秒检查;查询出错时暂停自动轮询。

提交响应丢失时,页面保留原请求键与输入,再次提交沿用该键。也可刷新任务列表确认是否已保存。更换文件或处理参数才生成新请求键;不要在未知结果时直接改参数或建立新请求来代替检查。

接口与配置 ​

tasks.submit/list/get/cancel/retry 均要求有效个人账号,用新鲜数据库核对所有权,组织切换不会扩大任务范围。变更请求的 Origin 必须匹配 APP_ORIGIN。列表使用 createdAt + id 游标,每页二十条,重复时间不会漏页;详情和列表只返回公开状态,不返回租约、输入摘要、队列 token 或请求指纹。越权查询 / 修改返回不存在。

packages/core/website.ts 的 tasks 控制 enabled、maxActive、maxAttempts、writesPerMinute、readsPerMinute。关闭新执行仍保留查询与取消。新提交 / 重试还必须存在 STORAGE 与 TASK_QUEUE,缺失时明确报错,不创建任务;开启任务同时需开启文件存储。个人写入 / 读取使用各自原子计数,批量请求逐项计算,不能通过批量打包绕过限制。

全站 Turnstile 开启时,提交动作固定为 task_submit,手动重试为 task_retry;取消不消费验证码。token 作为独立请求字段传到服务端验证,不进入请求摘要或数据库。页面显示的只有公开 site key / action,复用文件上传的 SDK 生命周期。真实 widget 仍待环境验收,默认关闭。

tRPC 错误含稳定的 taskError、fileError 或 executionError;限流带 retryAfterSeconds: 60。REQUEST_CONFLICT 检查请求键是否用于不同输入;ACTIVE_LIMIT 先结束或取消旧任务;NOT_RETRYABLE 检查状态与次数;DISABLED / NOT_CONFIGURED 核对策略与绑定。服务器异常只返回通用提示,不返回内部诊断消息 / 调试堆栈;任务入口也不记录原始错误体。实际接口异常不会自动再提交或清零执行预算。

自动验收命令 ​

sh
bun run test -- --run apps/api/lib/tasks/tasks.test.ts apps/api/lib/tasks/results.test.ts
bun tasks:validate-store
bun tasks:validate
bun files:validate

Vitest 使用实际迁移的 PGlite 和本地 R2,覆盖幂等冲突、个人隔离、五个活跃名额、输入保留、投递失败 / 迟到确认、并发领取、旧版本写回、有限重试、取消与未知处理器暂停。

tasks:validate-store 只允许 development 与 loopback PostgreSQL。它创建临时数据库并应用迁移,通过八个不同 PostgreSQL 会话测试真实锁等待、20 轮输入 / 删除竞争、幂等、次数与名额限制;实际 R2 保存测试输入。结束销毁临时数据库 / R2,正常配置指向的数据库不迁移。输出明确标记:没有测试业务队列消费者。

results.test.ts 使用实际迁移的 PGlite 与 R2,验证并发 / 重复执行只发布一次、精确字节与摘要、零字节结果、未发布隔离、过期版本拒绝、运行中取消、失败补偿 / 配额、输入实际摘要、到期保留、空间不足后的有限重试,以及提交成功但响应丢失时不误删结果。PGlite 不能代替真实多连接锁竞争,后者仍由 tasks:validate-store 验证。

tasks:validate 使用当前生产 Worker bundle、临时 PostgreSQL 和实际本地 Queue / R2,验证登录后的真实 HTTP 提交 / 幂等 / 列表 / 详情 / 取消 / 重试及越权 404,消费后私有下载核对原字节 / 摘要 / 响应头。重复消息和已取消消息不重复执行;失联 / 故障状态由内部受控夹具建立,再由实际维护 / 重试入口恢复。死信验收只在临时数据库安装拒绝领取的故障触发器,实际配置经历首次投递及三次十秒重投,保存一次收据并停止自动补投;移除故障后经真实 HTTP 重试恢复和下载。另一个临时数据库故障让实际消费者领取后失联,显式推进租约期限,实际维护 / 队列三轮后耗尽保存预算并拒绝手动重试。生产代码没有验收故障开关,也没有缩短实际队列重投配置。停止该验收 Worker、复用其持久目录重启后,原结果仍可下载且新任务继续执行。结束销毁临时数据库与资源目录,不代表浏览器或真实验证码验收。

files:validate 继续验证真正 Worker bundle 的文件流程和定时清理,使用另一个临时数据库。内部服务先保留测试文件,实际 HTTP 删除返回 409,内部取消后删除成功。额外保护检查消费真实写入次数,脚本核对 429 / 重试提示后,仅推进临时用户的计数窗口继续检查,不改变配置上限。

routers/tasks.test.ts 通过实际迁移 PGlite / R2 验证个人隔离、禁用 / 缺绑定、来源拒绝、幂等、发送失败保留、相同时间分页、并发限流和验证码动作;真正 tRPC HTTP 批量读取验证每项计数与错误格式。组件测试验证丢失响应沿用请求键、参数变更换键、断线禁止旧状态操作、重连显示结果和运行中取消等待结算。主预览浏览器已完成实际提交、状态轮询、结果预览 / 下载 / 摘要、直接详情刷新;320 / 390 像素任务 / 文件布局已检查实际宽度,无横向溢出,文件长文本对话框在 390 像素保留 64 KiB 提示。

两份新生成站分别在 8410 / 9410 独立安装、应用迁移并构建,使用不同数据库、认证命名空间和 R2 / Queue 名称。两站实际网关接口与浏览器都完成任务、预览 / 下载和直接详情刷新;混合 Cookie 选择本站会话,外站任务 / 取消 / 结果拒绝,详见迭代记录。生成时源仓库含本轮未提交改动,template.json 如实记录该情况,不将其声称为已发布版本。真实执行验证码、云端负载与第三方处理器仍保留后续验收门槛。

供语言模型读取:llms.txt · llms-full.txt
采用 MIT 许可证。