Pasty 使用说明
团队自建的轻量分享板。文本或文件丢上去,拿到一个 6 位共享码,凭码即取,到期自毁。
23456789ABCDEFGHJKMNPQRSTUVWXYZ。去掉了易混淆的 0/O/1/I/L,口头报码不会听错;输入时大小写随意。1它是什么
| 特性 | 事实 |
|---|---|
| HTML 页面 | 给 AI 用:凭令牌托管一个 HTML 页面,得到一个点开即看的 /p/ 网址(见 §9) |
| 登录 | 不需要。创建和查看都不鉴权,持码即可看 |
| 内容类型 | 纯文本 / 单个文件 / 文本 + 文件 |
| 文本上限 | 50,000 字符(JSON 接口另受 100KB 请求体限制,见 §4.2) |
| 文件上限 | 100 MB,单个文件,任意类型 |
| 过期 | 5 分钟 / 30 分钟 / 1 小时 / 1 天 / 7 天;默认 1 天 |
| 删除 / 编辑 / 列表 | 都没有。创建后不可撤回,只能等过期 |
| 存储 | 元数据和文本在 Redis(带 TTL),文件在 Cloudflare R2 |
适合:跨设备传一段文字或一个文件、面对面口头报一个码、把 Pod / 服务器上的产物交到远端用户手里、给客户发一个短期有效的下载链接。
不适合:需要撤回的内容、需要长期保存的内容、密钥 / 凭证 / 隐私数据(见 §7)。
2网页使用
2.1 创建粘贴
打开 pasty.youlidao.ai,默认在「创建粘贴」标签。
- 文本:在文本框输入或粘贴。右下角实时显示
已用 / 50,000,超限标红且按钮禁用。 - 文件(可选):拖到虚线区域、点击虚线区域选择,或焦点不在文本框时按
⌘V(WindowsCtrl+V)粘贴剪贴板里的图片。只能带一个文件,一次拖多个会提示「仅支持上传单个文件」并只取第一个。图片显示缩略图,其他类型显示图标、文件名、大小、MIME,点 × 移除。 - 过期时间:下拉框,默认「1 天」。
- 点「创建粘贴」。带文件时按钮变成「上传中 N%」并有进度条。文本超 50,000 字或文件超 100MB 在本地直接拦下。
- 结果页:大号共享码、「此内容将在 X 后过期」、「复制共享码」、「创建新粘贴」。
2.2 查看粘贴
- 输入码:首页切到「查看粘贴」,输入 6 位码。输入框自动转大写并过滤非法字符——被丢掉的字符会给出提示而不是静默消失;输满 6 位后 0.5 秒自动查询并跳转,也可以直接按「查看」。码不存在或已过期会提示「内容不存在或已过期」并清空输入框。
- 直接打开链接:
https://pasty.youlidao.ai/{CODE},大小写随意。
2.3 查看页长什么样
右上角是相对过期时间(「4 分钟后过期」「3 小时后过期」「6 天后过期」)。下面按内容类型呈现:
| 内容 | 呈现 |
|---|---|
| 文本 | 等宽 <pre> 原样展示(保留换行和缩进),下方「复制内容」按钮 |
| 图片 | png / jpeg / gif / webp / svg 页面内直接显示,点击在新标签打开原图;下方是文件名和大小 |
| 页面内嵌浏览器自带阅读器,旁边有「新标签打开」和「下载文件」。iOS Safari 内嵌框只显示第一页,点「新标签打开」看全文 | |
| 其他类型 | zip、Word、Excel、视频等:文件卡片(图标、文件名、大小、MIME)+「下载文件」按钮 |
| 文本 + 文件 | 先文本,再一条「📎 附件」分隔线,再文件 |
请求超过 10 秒未返回会弹「请求超时,请重试」。
2.4 分享给别人的三种形式
| 形式 | 例子 | 适用 |
|---|---|---|
| 只报码 | MN3W2R | 口头 / 手写 / 电话,对方自己到首页输 |
| 查看页链接 | https://pasty.youlidao.ai/MN3W2R | 微信 / 邮件,点开就是完整页面 |
| 文件直链 | https://pasty.youlidao.ai/api/pastes/MN3W2R/file | 图片和 PDF 点开即渲染;其他类型点开即下载。适合塞进 <img> 或 wget |
3命令行 / 脚本使用
3.1 发一段文本
curl -s -X POST https://pasty.youlidao.ai/api/pastes \
-H 'Content-Type: application/json' \
-d '{"content":"要分享的文本","expiresIn":"1h"}'
# → {"code":"7FZEN4","expiresAt":"2026-09-09T16:56:23.246Z"}
内容来自文件时用 jq 拼 JSON,避免手工转义:
jq -n --rawfile c notes.md '{content:$c, expiresIn:"1d"}' \
| curl -s -X POST https://pasty.youlidao.ai/api/pastes \
-H 'Content-Type: application/json' --data @-
3.2 发一个文件(可附说明)
curl -s -m 120 -X POST https://pasty.youlidao.ai/api/pastes \
-F "file=@/path/报告.pdf" \
-F "content=一句话说明这是什么" \
-F "expiresIn=1h"
# → {"code":"KTCK33","expiresAt":"…","hasFile":true}
- 多个文件先
zip -r打包成一个再传。文件接口限流 5 次/分钟/IP,不要循环逐个上传。 - 不用手动指定
type=:服务端按文件内容判定 MIME(§4.4)。 - 文件名支持中文和 Unicode,原样保留。
3.3 取回
# 元数据 + 文本
curl -s https://pasty.youlidao.ai/api/pastes/KTCK33
# 只要文本
curl -s https://pasty.youlidao.ai/api/pastes/KTCK33 | jq -r .content
# 下载文件(-J 用服务端给的文件名,-O 落盘)
curl -s -O -J https://pasty.youlidao.ai/api/pastes/KTCK33/file
没有 /raw 端点。GET /{CODE} 返回的是前端 SPA 的 HTML。脚本取文本一律走 /api/pastes/{CODE} 再取 .content。
3.4 两台机器之间传文件的推荐流程
# 发送端
shasum -a 256 FILE
curl -s -X POST https://pasty.youlidao.ai/api/pastes -F "file=@FILE" -F "expiresIn=30m"
# 接收端
curl -s -O -J https://pasty.youlidao.ai/api/pastes/CODE/file
shasum -a 256 FILE # 与发送端比对
传输本身无损(实测 zip / png 往返字节一致),校验是为了防传错码或下到一半断线。
如果所处环境只能发 JSON 文本(老脚本、受限沙箱),可退回 base64 通道:base64 -i FILE | tr -d '\n' 作为 content 发,接收端 base64 -d。base64 后体积 ×1.33 且受 100KB 请求体限制,只适合小文件。
4API 参考
4.1 端点总览
| 方法 | 路径 | 作用 | 限流(按 IP) |
|---|---|---|---|
GET | /api/health | 健康检查 | 无 |
POST | /api/pastes | application/json = 纯文本;multipart/form-data = 文件 ± 文本 | JSON 10 次/分钟;multipart 5 次/分钟,两个计数器独立 |
GET | /api/pastes/:code | 取元数据 + 文本 | 无 |
GET | /api/pastes/:code/file | 流式下载 / 预览文件 | 无 |
POST | /api/pages | 托管一个 HTML 页面(需令牌):JSON html 或 multipart file | 10 次/分钟 |
GET | /p/:code | 在沙箱 CSP 下渲染托管页面 | 无 |
GET | /p/:code/pdf | 把托管页面导出为 A4 PDF(首次渲染后缓存) | 10 次/分钟 |
每次创建响应都带限流头:ratelimit-limit、ratelimit-remaining、ratelimit-reset(秒)、ratelimit-policy(如 10;w=60)。超限返回 429,body 的 code 字段为 RATE_LIMITED(文本路径)或 FILE_RATE_LIMITED(文件路径)。
4.2 POST /api/pastes · JSON 路径
{ "content": "必填,去首尾空白后非空,≤ 50,000 字符",
"expiresIn": "5m | 30m | 1h | 1d | 7d,可省略,默认 1d" }
// 201
{ "code": "7FZEN4", "expiresAt": "2026-09-09T16:56:23.246Z" }
请求体上限 100KB。这是 Express 的 JSON 解析限制,在字符数校验之前生效:40,000 个汉字约 120KB,会得到 413 request entity too large。中文长文本要么控制在约 33,000 字以内,要么改走 multipart 路径的 content 字段(无此限制,完整 50,000 字可用)。
校验失败的格式(details 仅 JSON 路径有):
{ "error": "Content must not be empty",
"code": "CONTENT_EMPTY",
"details": [ { "path": "content", "message": "CONTENT_EMPTY" } ] }
4.3 POST /api/pastes · multipart 路径
| 字段 | 必填 | 规则 |
|---|---|---|
file | 与 content 至少一个 | 单个文件 ≤ 100MB(104,857,600 字节),任意类型 |
content | 与 file 至少一个 | 去首尾空白后非空,≤ 50,000 字符 |
expiresIn | 否 | 同上,默认 1d |
响应 201:{ "code", "expiresAt", "hasFile": true },无文件时没有 hasFile 字段。处理顺序:先传 R2,成功后才写 Redis;R2 失败不写 Redis;Redis 失败会回滚删除 R2 对象。
文件名规则:
- 超过 255 字符 → 400
FILENAME_TOO_LONG - 含
../、..\或其 URL 编码形式 → 400FILENAME_INVALID,并记安全日志 / \ : * ? " < > |替换为_,去掉开头的点和空格- 空文件名 → 按 MIME 补成
unnamed.png之类 - 中文 / Unicode 原样保留,此前会变成 latin1 乱码
4.4 MIME 判定:决定「预览」还是「下载」
服务端不直接采信客户端声明的 Content-Type,按以下顺序确定:
- 文件魔数(图片、PDF、zip、Office、音视频等二进制格式)
- 客户端声明,仅当非空且不是
application/octet-stream(覆盖 txt / csv / json / svg 这类无魔数的文本格式) - 文件扩展名
application/octet-stream
效果:curl -F file=@x.zip 存为 application/zip;无扩展名、无声明的 PDF 照样识别为 PDF 并可内联预览;把 PDF 改名成 .txt 上传仍按 PDF 处理。
4.5 GET /api/pastes/:code
code 必须恰好 6 位且全部落在字符集内,否则 400;服务端统一转大写。
{
"content": "说明文字(纯文件时为空串)",
"type": "text | file | mixed",
"fileMetadata": {
"fileName": "测试图片.png", "fileSize": 74, "mimeType": "image/png",
"storageKey": "pastes/U9RBRW/测试图片.png"
},
"createdAt": "2026-09-09T17:00:41.045Z",
"expiresAt": "2026-09-09T17:05:41.045Z"
}
type 和 fileMetadata 只在有文件时出现;早期纯文本记录可能连 type 都没有,按 text 处理。
4.6 GET /api/pastes/:code/file
流式返回文件本体,不经内存缓存。响应头:
| 头 | 值 |
|---|---|
Content-Type | 判定后的 MIME |
Content-Disposition | image/* → inline;PDF → inline; filename=…;其他 → attachment; filename=…(RFC 5987 编码,中文名安全) |
Content-Length | 文件大小 |
Content-Security-Policy | 内联响应(图片 / PDF)在站点基线上追加 sandbox,脚本一律不执行;附件响应保持基线 |
4.7 错误码总表
每个错误响应都是 { "error": <英文说明>, "code": <标识符> }。程序判断请用 code:它是稳定契约;error 是给人读的英文散文,措辞可能变。网页端不显示 error,它按 code 查本地文案,用访问者自己的语言呈现。
| 状态 | code | 场景 |
|---|---|---|
| 400 | CONTENT_EMPTY | content 为空或全为空白 |
| 400 | INVALID_EXPIRY | expiresIn 不在 5m/30m/1h/1d/7d 内 |
| 400 | INVALID_CODE_FORMAT | 共享码不是 6 位合法字符 |
| 400 | NO_CONTENT_PROVIDED | multipart 里 content 和 file 都没有 |
| 400 | FILENAME_TOO_LONG / FILENAME_INVALID | 文件名超 255 字符 / 含路径遍历 |
| 400 | VALIDATION_ERROR | multipart 损坏,或其他未细分的校验失败 |
| 400 | PP_SLUG_INVALID | 链接后缀格式、长度不符,或是保留路径 |
| 401 | PAGE_AUTH_REQUIRED | 创建页面时没带令牌,或令牌不对 |
| 401 | PP_AUTH_REQUIRED | 创建永久页面未带令牌,或令牌错误 |
| 404 | PASTE_NOT_FOUND | 码不存在或已过期 |
| 404 | NO_FILE_IN_PASTE | 该码是纯文本,没有文件 |
| 404 | FILE_NOT_FOUND | R2 对象已被清理 |
| 413 | CONTENT_TOO_LARGE | 文本超 50,000 字,或 JSON 请求体超 100KB |
| 413 | FILE_TOO_LARGE | 文件超 100MB |
| 413 | PAGE_TOO_LARGE | HTML 超过 5MB |
| 409 | PP_SLUG_TAKEN | 该后缀已被其他永久页面或正在使用的共享码占用 |
| 429 | RATE_LIMITED / FILE_RATE_LIMITED | 文本创建限流 / 文件上传限流,两者独立计数 |
| 501 | FILE_UPLOAD_DISABLED | 服务端未配置 R2 却收到文件 |
| 501 | PAGE_HOSTING_DISABLED | 服务端没配置 PAGE_API_TOKENS,页面托管处于关闭状态 |
| 501 | PP_HOSTING_DISABLED | 服务端未设置 PP_API_TOKENS,永久页面功能未开启 |
| 503 | SERVICE_UNAVAILABLE / SERVICE_BUSY | Redis 故障 / 共享码碰撞重试 5 次仍失败 |
| 503 | STORAGE_UNAVAILABLE / FILE_UPLOAD_FAILED | R2 不可用 / 上传失败 |
| 503 | PDF_EXPORT_UNAVAILABLE | PDF 渲染失败、排队已满,或服务端未配置 Chromium |
| 500 | INTERNAL | 未捕获的服务端异常 |
5与 Claude Code / cc-hub 的配合
youlidao.ai 的 cc-hub 会话和建造中心 Pod 上已预装 pasty-share skill(v3)。用户对 AI 说「把 XX 发我」「这个文件给客户」时,AI 会:
- 用 multipart 把原始文件本体传上来。skill 里的铁律:禁止把 PDF / Word 转成 markdown 再发
- 默认
expiresIn=1h,多个文件先打 zip - 回复共享码 + 查看链接;图片和 PDF 再附一条 /file 直链
- 每次明示过期时间和「持码任何人可见」
skill 的安全红线:不传 .env、私钥、token、凭证、数据库导出;内容可能敏感先问用户;用户坚持要传就用 5m / 30m。
6数据生命周期
| 阶段 | 发生什么 |
|---|---|
| 创建 | Redis 写 paste:{CODE},TTL = 所选过期秒数;有文件则先写 R2 pastes/{CODE}/{文件名} |
| 有效期内 | 无限次读取,不计数 |
| 到期 | Redis key 自动消失,查询即 404 |
| 到期后清理 | 服务端每小时扫一遍,删掉已过期记录对应的 R2 对象;R2 侧另有生命周期规则兜底(文档记为 8 天) |
| 日志 | 结构化日志只记 IP、大小、MIME、净化后的文件名,不记文本内容 |
共享码由 CSPRNG 经拒绝采样均匀生成,31 的 6 次方 = 887,503,681 种组合,碰撞时重试最多 5 次。
7安全边界,使用前必读
- 没有鉴权:码就是唯一的钥匙,任何拿到码或链接的人都能看。链接发进群 = 全群可见。
- 无法撤回:发错了只能等过期。不确定就选 5 分钟或 30 分钟。
- 不要传:密码、API key、私钥、证书、.env、含个人信息的数据。
- 上传内容不会被执行:图片 / PDF 内联响应带 sandbox CSP,即使上传的是带脚本的 SVG 也不会跑(真浏览器实测过)。HTML、zip 等一律作为附件下载而非渲染。
- 页面不能被别的站点 iframe 嵌入(
frame-ancestors 'self')。 - 内容页不被搜索引擎收录:robots.txt 禁止抓取共享码路径,响应另带
X-Robots-Tag: noindex。这挡的是爬虫,不是拿到码的人。 - 托管的 HTML 页面(§9)只在
/p/{CODE}下渲染,带独立沙箱 CSP:不同源、不能调接口、不能提交表单。创建需要令牌。
8常见问题
- 浏览器打开提示 ERR_CONNECTION_CLOSED,或 curl 返回 000
- 国内直连偶发,刷新或重试一次即通。健康检查和读取接口本身稳定。
- 上传按钮一直灰着
- 文本为空且没选文件,或文本超过 50,000 字(计数器变红)。
- PDF 在手机上只看到第一页
- iOS Safari 内嵌框的限制,点「新标签打开」。
- 中文文件名下载下来是乱码
- 2026-09-10 之前上传的记录会这样(服务端曾按 latin1 解码文件名)。此后上传的已修复;老记录到期自然消失。
- zip 用 curl 传上去以前显示 application/octet-stream
- curl 不认识 .zip 就声明 octet-stream,旧版直接采信。现在按文件内容判定,不需要再手动
;type=。 - 能不能改内容、删掉、延长时间
- 不能,没有这些接口。重新创建一个。
- 返回 429 怎么办
- 看响应头
ratelimit-reset(秒)等一下。文本 10 次/分钟,文件 5 次/分钟,按 IP 计。多个文件打包成一个 zip。 - 中文长文本 JSON 发不出去,返回 413 request entity too large
- JSON 请求体上限 100KB。改用 multipart 的 content 字段,或分段。
- 界面能换语言吗
- 能。页脚有语言选择(简体中文 / English / 日本語 / 한국어),选择记在本地;也可以用
?lang=en之类直接指定。
9HTML 页面托管:给 AI 用的临时网页
9.1 它解决什么
AI 生成了一份报告、看板或原型,需要给人一个点开就能看的网址,而不是一个要下载的文件。把一个 HTML 文档 POST 上来,拿到 https://pasty.youlidao.ai/p/{CODE},到期自毁。
| 特性 | 事实 |
|---|---|
| 谁能创建 | 持令牌者:请求头 Authorization: Bearer <token>。服务端没配置令牌时接口返回 501,功能处于关闭状态 |
| 谁能看 | 和其他内容一样,持码 / 持链接即可看,不用登录 |
| 大小 | 单个 HTML 文档 ≤ 5 MB |
| 过期 | 5m / 30m / 1h / 1d / 7d,默认 1 天 |
| 共享码 | 和文本、文件共用一个码空间。/{CODE} 显示一张页面卡片,/p/{CODE} 直接渲染页面 |
| 限流 | 10 次/分钟/IP |
9.2 创建页面
curl -s -X POST https://pasty.youlidao.ai/api/pages \
-H "Authorization: Bearer $PASTY_PAGE_TOKEN" \
-F "file=@report.html" \
-F "expiresIn=7d"
# → {"code":"MN3W2R","expiresAt":"…","url":"https://pasty.youlidao.ai/p/MN3W2R"}
HTML 已经在程序变量里时,用 JSON 路径,字段名是 html:
jq -n --rawfile h report.html '{html:$h, expiresIn:"1d"}' \
| curl -s -X POST https://pasty.youlidao.ai/api/pages \
-H "Authorization: Bearer $PASTY_PAGE_TOKEN" \
-H 'Content-Type: application/json' --data @-
- 响应 201:
{ "code", "expiresAt", "url" }。url就是发给人的链接。 - JSON 路径的请求体上限是页面上限的 2 倍,给转义留余量;接近 5MB 的页面请用 multipart。
- 令牌放在环境变量里,不要写进页面、日志或聊天记录。
9.3 打开页面时的运行环境
/p/{CODE} 以 text/html; charset=utf-8 返回,并带一条专用 CSP:
| 可以 | 不可以 |
|---|---|
| 运行内联脚本,以及从这些 CDN 加载脚本:cdnjs.cloudflare.com, cdn.jsdelivr.net, unpkg.com, cdn.tailwindcss.com | 调接口:fetch / XHR / WebSocket 全部禁止(connect-src 'none') |
| 内联样式、Google Fonts、data: 字体 | 提交表单(form-action 'none') |
| 任意 https / data: / blob: 的图片和音视频 | 嵌入 iframe、object |
| 弹窗,新标签打开外部链接 | 读写 localStorage / cookie:页面运行在独立沙箱里(不同源),访问会抛异常 |
沙箱的作用:页面脚本不能冒充 pasty 本站,也拿不到本站的任何存储。禁止外发数据,是为了让页面没法当钓鱼表单用;但这不是绝对的(图片地址、页面跳转仍能带出数据),真正的门槛是创建时的令牌。
码不存在、已过期,或者对应的是文本 / 文件而不是页面时,会 302 跳到 /{CODE},由普通查看页说明情况。通过匿名文件接口上传的 .html 永远只作为附件下载,不会在 /p/ 下渲染。
9.4 给 AI 的写页面清单
- 单文件:CSS 和 JS 内联;图片用 data: URI 或 https 外链;库只从 cdnjs.cloudflare.com, cdn.jsdelivr.net, unpkg.com, cdn.tailwindcss.com 加载,并锁定版本号。
- 不依赖网络请求:数据直接写进页面,不要再去 fetch JSON。
- 不用 localStorage;非用不可就包 try/catch,页面没有它也要能正常显示。
- 写上
<meta charset="utf-8">和 viewport,手机上要能看。国内访问 Google Fonts 不稳定,字体要写好系统字体回退。 - 过期默认用
1d,给人看的报告可以用7d。回复用户时给出url和过期时间,并说明「拿到链接的人都能看」。 - 不放密钥、个人隐私或内部凭证,规则同 §7。
给机器读的完整说明见 /llms.txt。
9.5 导出 PDF
每个托管页面右下角都有一个「导出 PDF」按钮(读者浏览器语言不是中文时显示 Export PDF),对应 /p/{CODE}/pdf,也可以直接把这个地址发给别人。
- 服务端用 Chromium 打开同一个
/p/{CODE},在同样的沙箱规则下渲染成 A4 PDF:保留背景色,固定按浅色模式输出,按钮本身不会出现在 PDF 里。 - 第一次导出需要几秒到十几秒;生成后缓存,之后直接返回。PDF 和页面同时过期、一起清理。
- 以
inline返回并带文件名:手机和微信内置浏览器里直接打开预览,电脑上可以另存。 - 限流 10 次/分钟/IP。渲染失败返回 503
PDF_EXPORT_UNAVAILABLE,重试即可。 - 页面要想导出效果好:内容不要依赖鼠标悬停或点击后才显示;宽表格放在可横向滚动的容器里,PDF 里会按 A4 宽度换行。
10运维备忘(给维护者)
| 项 | 值 |
|---|---|
| 仓库 | ~/Codes/pasty,pnpm monorepo:client / server / shared,分支 main |
| 部署 | Railway 项目 pasty / 服务 pasty-app + Redis 插件;push 到 main 自动构建。2026-10-16 起用仓库根目录的 Dockerfile 构建(Debian bookworm + Chromium + Noto CJK 字体,供 PDF 导出用),不再走 nixpacks |
| 构建命令 | 见 Dockerfile:pnpm i --frozen-lockfile --prod=false && pnpm run build;本地可用 docker build -t pasty . 复现线上镜像 |
| 健康检查 | GET /api/health → {status, redis, r2, uptime};uptime 归零 = 刚重启 |
| 环境变量 | PORT、REDIS_URL、NODE_ENV=production(必须,决定是否托管前端)、RATE_LIMIT_MAX(默认 10)、RATE_LIMIT_FILE_MAX(默认 5)、R2_ACCOUNT_ID / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_BUCKET_NAME、PAGE_API_TOKENS(逗号分隔的页面令牌,留空 = 页面托管关闭)、RATE_LIMIT_PAGE_MAX(默认 10)、PUBLIC_ORIGIN(拼返回的页面 URL,默认 https://pasty.youlidao.ai)、CHROMIUM_PATH(PDF 导出用,Dockerfile 里已设为 /usr/bin/chromium;留空 = 导出关闭、页面不显示按钮)、RATE_LIMIT_PDF_MAX(默认 10) |
| 测试 | 根目录 pnpm test(vitest,Redis / R2 全 mock,不需要本地服务) |
| 本页 | 由 client/landing/guide/ 生成:structure.mjs 是骨架与共享代码片段,content/<lang>.mjs 只放散文。端点表和错误码表由代码生成,数字用占位符从常量注入 |
| 查部署状态 | Railway CLI 本机常 TLS 失败;用 ~/.railway/config.json 的 token 直打 GraphQL deployments / buildLogs。登录过期会返回 Not Authorized,重新 railway login |
变更记录
- 2026-10-16:托管页面新增 PDF 导出(§9.5):页面右下角「导出 PDF」按钮,
/p/{CODE}/pdf由服务端 Chromium 渲染并缓存到 R2;构建方式从 nixpacks 改为 Dockerfile。 - 2026-10-16:新增 HTML 页面托管(§9):
POST /api/pages凭令牌创建,/p/{CODE}在独立沙箱 CSP 下渲染(不同源、禁止调接口和提交表单);新增/llms.txt;补上客户端缺失的 Vite 类型声明,tsc --noEmit恢复通过。 - 2026-09-18:界面与 API 错误多语种化(中 / 英 / 日 / 韩);API 错误响应新增稳定的
code字段,error散文转英文;站点加上 robots.txt、sitemap、结构化数据与各语种落地页;本页改为按语种生成。 - 2026-09-17:共享码生成改用拒绝采样消除取模偏置;共享码输入框丢弃易混字符时给出提示而非静默消失。
- 2026-09-10:multipart 文件名按 UTF-8 解码,中文文件名不再乱码;MIME 按魔数 → 声明 → 扩展名判定,不再直接采信客户端。