PictShare 3.7.5 URL 参数与 HTTP API 详解
逐条实测 PictShare 3.7.5 的 HTTP API 与 URL 路径变换:上传四形态、info 查询、删除双通道、相册、滤镜转换与 MCP 端点。
PictShare 3.7.5 URL 参数与 HTTP API 详解
目标:把 PictShare 3.7.5 对外的全部接口一次讲清并逐条实测——
/api/upload四种上传形态、/api/info查询、删除双通道、相册创建、URL 路径变换(缩放/旋转/34 种滤镜/格式转换/raw)、/api/passthrough直传变换与 MCP 端点,沉淀错误码速查与排障实录。已在 Ubuntu 22.04 服务器上对 PictShare 3.7.5 实测通过,全部请求与响应均为实测原文。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64,内核 5.15+)
- 软件版本:PictShare 3.7.5(官方最新稳定版 tag
v3.7.5,2026-10-27 发布,查询日期 2026-10-31 无更新) - 服务端:PHP 8.4.27 + FrankenPHP(Caddy 内嵌 PHP)、内置 Redis、ffmpeg
- 实测实例:
http://192.168.146.128:8131/(按《PictShare 3.7.5 Docker Compose 部署指南》部署,默认配置:未设 UPLOAD_CODE,全部内容控制器启用) - 客户端工具:
curl8.5.0(含-F表单上传、-D -响应头打印)、file命令(产物尺寸验证) - 示例文件:
test.png(800×600 PNG)、test.jpg、test.gif(动图)、test.mp4、文本片段 - 开源许可:Apache License 2.0
二、前置条件
- 已有可访问的 PictShare 3.7.5 实例(部署见《PictShare 3.7.5 Docker Compose 部署指南》,实例选型与能力边界见《PictShare 介绍与选型指南》);
- 客户端能
curl到实例端口(本文全部请求即 curl 原文); - 若实例配置了
UPLOAD_CODE/ALLOWED_SUBNET,先读第四章认证模型;参数本身详解见《PictShare 3.7.5 配置参数详解》。
三、API 总览
PictShare 3.x 的对外接口分三张面,共享同一套「JSON 应答 + hash 寻址」约定:
| 接口面 | 形态 | 典型用途 |
|---|---|---|
REST 风格 /api/* | HTTP POST/DELETE + JSON | 程序化上传、查询、删除、建相册、直传变换 |
| URL 路径变换 | 改 URL 路径即变换(/300x200/hash.png) | 前端/Markdown 直接外链缩略图、滤镜、转码 |
MCP 端点 /mcp | JSON-RPC(LLM 代理协议) | Agent 工具化调用上传/查询/删除 |
三条全局约定:
- hash 即地址:每个文件以内容寻址 hash(如
mrokk1.png)为唯一标识,URL 末段必须是完整文件名(hash+扩展名),变换参数只能拼在文件名之前; - JSON 应答:
/api/*全部返回 JSON,成功status:ok,失败status:err+reason; - 无分页无列表:没有「列出全部文件」的接口(隐私优先设计),一切操作围绕已知 hash 进行。
💡 技术拓展:
/api/*属「REST 风格 HTTP API」。同类接口风格横向对比——
风格 特征 代表 RESTful 资源化 URL + 标准 HTTP 方法语义 本文的 /api/info/{hash}、DELETE /api/delete/{code}/{hash}RPC over HTTP 动作即端点( geturl.php、pastebin.php)PictShare v1/v2 的旧接口(3.x 已移除,见 5.6) GraphQL 单端点 + 查询语言 图数据库/聚合网关常用 PictShare 3.x 完成了从 RPC 风格到 REST 风格的迁移:老式
*.php端点全部下线,动作并入资源化 URL,本文全部按 3.7.5 实际行为书写。
四、认证与首次调用
4.1 默认无认证:第一次上传
默认配置(未设 UPLOAD_CODE、来源不受 ALLOWED_SUBNET 限制)下,接口完全开放。最小上传请求(test.png 为本地 800×600 PNG,实测回显):
curl -s -F "file=@test.png" http://192.168.146.128:8131/api/upload
{
"status": "ok",
"hash": "mrokk1.png",
"filetype": "image\/png",
"url": "http:\/\/192.168.146.128:8131\/mrokk1.png",
"delete_code": "pj6q3f3i9awmp6kx7hyjt2y5idjnzhda",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_pj6q3f3i9awmp6kx7hyjt2y5idjnzhda\/mrokk1.png"
}
六个返回字段的含义:
| 字段 | 含义 | 后续怎么用 |
|---|---|---|
| status | ok/err | err 时看 reason |
| hash | 内容寻址标识,带真实扩展名 | 拼 URL 外链、变换、查询、删除都靠它 |
| filetype | 服务端按内容探测的 MIME | 确认入库类型(不看扩展名) |
| url | 外链直址(URL 环境变量前缀 + hash) | 直接引用 |
| delete_code | 32 位随机删除码 | 删除凭证,只在响应里出现一次,丢了无法补 |
| delete_url | 即席删除 URL(delete_code + hash 拼装) | 浏览器打开即删 |
4.2 认证模型:UPLOAD_CODE 与 ALLOWED_SUBNET
开启 UPLOAD_CODE 后,上传类接口(upload/album)必须带 uploadcode 字段(GET/POST 均可)。三态实测(UPLOAD_CODE=s3cret 实例,实测回显):
# 不带口令
curl -s -F "file=@test.png" http://192.168.146.128:8133/api/upload
{
"status": "err",
"reason": "Incorrect upload code specified - Access denied"
}
# 口令错误(同上 reason);口令正确
curl -s -F "file=@test.png" -F "uploadcode=s3cret" http://192.168.146.128:8133/api/upload
{
"status": "ok",
"hash": "g8ir60.png",
"filetype": "image\/png",
"url": "http:\/\/192.168.146.128:8133\/g8ir60.png",
"delete_code": "3hsz0q8e0ewwgk2cpipmtz7k3ifl3x85",
"delete_url": "http:\/\/192.168.146.128:8133\/delete_3hsz0q8e0ewwgk2cpipmtz7k3ifl3x85\/g8ir60.png"
}
ALLOWED_SUBNET(CIDR)则在更前面拦截:来源 IP 不在网段内直接 Access denied。两者只锁「写操作」,读取(外链/变换/info)不受影响。
五、上传域 API:POST /api/upload 全形态
/api/upload 是唯一上传入口,按请求体不同自动分派四种形态,共用同一个端点。
5.1 multipart 文件上传(form-data)
标准形态,字段名 file(见 4.1)。同一内容重复上传会触发去重(实测回显,第二次上传同一 PNG):
{
"status": "ok",
"hash": "mrokk1.png",
"filetype": "image\/png",
"url": "http:\/\/192.168.146.128:8131\/mrokk1.png",
"duplicate": true
}
duplicate:true 表示内容已存在(SHA1 命中),服务器只存一份并返回已有 hash,不生成新删除码。文件类型按内容探测而非扩展名:上传一个改名为 .png 的文本文件,返回 filetype: text/plain,以 .txt hash 入库走文本仓(实测回显):
{
"status": "ok",
"hash": "swmvwi.txt",
"filetype": "text\/plain",
"url": "http:\/\/192.168.146.128:8131\/swmvwi.txt",
"delete_code": "yoqkidoehwcl8bmjznqd3fcqngkfzsev",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_yoqkidoehwcl8bmjznqd3fcqngkfzsev\/swmvwi.txt"
}
5.2 base64 上传
字段名 base64,接受裸 base64 或 data-URI(data:image/jpeg;base64,...)。实测(实测回显):
(echo -n "base64="; echo -n "data:image/jpeg;base64,$(base64 -w 0 test.jpg)") | curl --data @- http://192.168.146.128:8131/api/upload
{
"status": "ok",
"hash": "to4wdp.jpg",
"filetype": "image\/jpeg",
"url": "http:\/\/192.168.146.128:8131\/to4wdp.jpg",
"delete_code": "qp2gztnm10gxz4l8ehhrlwiuh72qcg3p",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_qp2gztnm10gxz4l8ehhrlwiuh72qcg3p\/to4wdp.jpg"
}
适合无法构造 multipart 的场景(脚本内嵌资源、数据库里读出的 BLOB)。
5.3 URL 抓取上传(url=)与 SSRF 防护
字段 url 让服务器代抓公网文件(网页 HTML 会转存为文本仓)。抓取一张公网图片(实测回显):
curl -s "http://192.168.146.128:8131/api/upload?url=http://www.libpng.org/pub/png/img_png/pnglogo-blk.png"
{
"status": "ok",
"hash": "iic32n.png",
"filetype": "image\/png",
"url": "http:\/\/192.168.146.128:8131\/iic32n.png",
"delete_code": "4jaic2ki5tqibwe0mr6nb72e5jif1akv",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_4jaic2ki5tqibwe0mr6nb72e5jif1akv\/iic32n.png"
}
抓取私网地址会被 SSRF 防护拒绝(实测回显):
curl -s "http://192.168.146.128:8131/api/upload?url=http://127.0.0.1:6379/"
curl -s "http://192.168.146.128:8131/api/upload?url=http://192.168.146.128/static/test.png"
{
"status": "err",
"reason": "Private IP range"
}
服务端在解析目标域名后校验解析出的 IP:私网段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)、环回段(127.0.0.0/8)及保留/链路本地段一律拒绝,且重定向的每一跳都会重新校验(防止"公网入口跳转到内网"绕过)。这是把 PictShare 暴露在公网时的关键安全线。
5.4 自定义 hash(hash= 字段)
请求附带 hash 字段可指定对外文件名(会被净化校验)。实测(实测回显):
curl -s -F "file=@test.png" -F "hash=my-custom-name.png" http://192.168.146.128:8131/api/upload
{
"status": "ok",
"hash": "my-custom-name.png",
"filetype": "image\/png",
"url": "http:\/\/192.168.146.128:8131\/my-custom-name.png",
"delete_code": "jt32iaj34t3dzvckuzamr724asienyhp",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_jt32iaj34t3dzvckuzamr724asienyhp\/my-custom-name.png"
}
注意:若同名/同内容文件已存在,返回的是已有文件的 JSON(duplicate:true),自定义名不会顶替旧文件。
5.5 上传错误码
| reason 原文 | 触发条件(实测) |
|---|---|
No file uploaded | 请求体里没有 file/base64/url 任一字段 |
Incorrect upload code specified - Access denied | 设了 UPLOAD_CODE 但未带/带错 uploadcode |
Access denied | 来源 IP 不在 ALLOWED_SUBNET |
Unsupported mime type: text/plain | CONTENTCONTROLLERS 白名单收缩后类型被拒 |
Private IP range | url= 指向私网地址(SSRF 防护) |
Invalid URL | url= 不是 http(s) 开头 |
Upload error: The uploaded file exceeds the upload_max_filesize directive in php.ini. | 超过 MAX_UPLOAD_SIZE(PHP 层拦截) |
5.6 v3 已移除的旧端点(防踩坑)
官方 API.md 仍记载的 api/geturl.php、api/pastebin.php、api/info.php 三个 RPC 风格端点在 3.7.5 已全部下线,逐个实测返回同一错误(实测回显):
curl -s 'http://192.168.146.128:8131/api/geturl.php?url=https://example.com'
curl -s -F 'api_paste_code=Hello World' http://192.168.146.128:8131/api/pastebin.php
curl -s 'http://192.168.146.128:8131/api/info.php?hash=mrokk1.png'
{
"status": "err",
"reason": "Unknown API call",
"hint": "Check https:\/\/github.com\/HaschekSolutions\/pictshare\/blob\/master\/rtfm\/API.md for more information"
}
迁移对照:geturl.php?url= → /api/upload?url=;pastebin.php → POST /api/upload 带 base64 或直接文本上传;info.php?hash= → /api/info/<hash>。写新集成代码一律用本章的 3.x 形态。
六、查询与调试域
6.1 GET /api/info/{hash}
返回文件公开元数据(mime/大小/sha1/上传时间;上传者 IP、删除码等敏感字段不在公开集合内)。三种情形实测(实测回显):
curl -s http://192.168.146.128:8131/api/info/mrokk1.png
{
"mime": "image\/png",
"size": 3295,
"size_human": "3.22 KB",
"hash": "mrokk1.png",
"sha1": "6fa85e1c71824d69849faf6d8ea5fb8bb21d0e86",
"uploaded": 1793352621
}
curl -s http://192.168.146.128:8131/api/info/ # 缺 hash
curl -s http://192.168.146.128:8131/api/info/zzzzzz.png # 不存在
{
"status": "err",
"reason": "Missing hash"
}
{
"status": "err",
"reason": "Hash not found"
}
/api/info 是删除后存活判定的唯一可靠手段(原因见第七章缓存残留)。
6.2 GET /api/debug
返回服务端视角的连接信息,用于排障反代链路(实测回显):
curl -s http://192.168.146.128:8131/api/debug
{
"server_name": "127.0.0.1",
"server_addr": null,
"remote_addr": "10.89.59.1",
"remote_port": "38114",
"HTTP_CF_CONNECTING_IP": null,
"HTTP_CLIENT_IP": null,
"HTTP_X_FORWARDED_FOR": null
}
remote_addr 是 TCP 对端,HTTP_X_FORWARDED_FOR 等头用于核对反代是否正确传递真实 IP(ALLOWED_SUBNET 与日志记录取的正是这些值)。
七、删除域:/api/delete 与 delete_ 路径
7.1 DELETE /api/delete/{code}/{hash}
REST 形态删除,code 为上传时返回的 delete_code。完整闭环实测(上传 → 查询 → 错码 → 正码 → 复查 → 外链状态,实测回显):
# 上传唯一内容(去重机制要求测试用全新文件)
curl -s -F "file=@u1.png" http://192.168.146.128:8131/api/upload
{
"status": "ok",
"hash": "hp49b8.png",
"filetype": "image\/png",
"url": "http:\/\/192.168.146.128:8131\/hp49b8.png",
"delete_code": "i8326ole8fw2i8ao16edurqmltsru9un",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_i8326ole8fw2i8ao16edurqmltsru9un\/hp49b8.png"
}
# 错误删除码
curl -s -X DELETE http://192.168.146.128:8131/api/delete/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/hp49b8.png
{
"status": "err",
"reason": "Invalid delete code"
}
# 正确删除码
curl -s -X DELETE http://192.168.146.128:8131/api/delete/i8326ole8fw2i8ao16edurqmltsru9un/hp49b8.png
{
"status": "ok",
"hash": "hp49b8.png"
}
curl -s http://192.168.146.128:8131/api/info/hp49b8.png # 复查
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://192.168.146.128:8131/hp49b8.png
{
"status": "err",
"reason": "Hash not found"
}
HTTP 404
同内容重传会得到新 hash(3.7.5 删除不写黑名单),不会被拦截。
7.2 即席删除 URL(delete_code 前缀路径)
上传响应里的 delete_url 是给浏览器的即席删除链接,curl 同样可用(实测回显):
curl -s "http://192.168.146.128:8131/delete_i8326ole8fw2i8ao16edurqmltsru9un/hp49b8.png"
hp49b8.png deleted successfully
(本例文件已在 7.1 删除故返回 not found 形态;对存活文件执行时输出如上 deleted successfully 一行。)两种删除形态校验同一份删除码,效果等价。
7.3 ⚠️ 缓存残留:删除后 URL 可能仍返回 200
删除后的外链状态与该 URL 是否被访问过有关(3.7.5 实测):
| 场景 | 删除后访问外链 |
|---|---|
| 删除前从未被访问过 | HTTP 404 |
| 删除前被访问过(Redis 已缓存 URL→hash 映射) | HTTP 200,但响应体仅 1 字节空内容 |
第二种的实测(上传 → 访问一次填缓存 → 删除 → 再访问):
-- first view (cache fill):
HTTP 200
{"status":"ok","hash":"ahplex.png"} # 删除成功
-- view after delete:
HTTP 200 # 缓存残留,body 为空
残留的 200 响应 Content-Type 仍是原类型但 body 为空字节,内容已不可用。监控与探测判据一律用 /api/info/<hash>(Hash not found 即已删除),不要用外链状态码。 该行为源于删除路径与 Redis 缓存清理键不匹配,升级版本时建议回归验证。
八、相册域:POST /api/album
把已存在的多个 hash 组合成不可变相册(缩略图画廊页),最多 200 个,成员只存引用不复制文件(实测回显):
curl -s -X POST \
-d "hashes[]=qms9c7.png" \
-d "hashes[]=my-custom-name.png" \
http://192.168.146.128:8131/api/album
{
"status": "ok",
"hash": "5i4z6sje.album",
"url": "http:\/\/192.168.146.128:8131\/5i4z6sje.album",
"count": 2,
"delete_code": "v68uxa9azhwwudespr02pdhu0u186w1q",
"delete_url": "http:\/\/192.168.146.128:8131\/delete_v68uxa9azhwwudespr02pdhu0u186w1q\/5i4z6sje.album"
}
错误形态实测(实测回显):
curl -s -X POST http://192.168.146.128:8131/api/album # 缺 hashes
curl -s -X POST -d "hashes[]=zzzzzz.png" http://192.168.146.128:8131/api/album # 含不存在 hash
{
"status": "err",
"reason": "Missing or empty hashes array"
}
{
"status": "err",
"reason": "Hash not found: zzzzzz.png"
}
相册不可变:要增删成员只能新建相册;删除相册(delete_url)不影响成员文件。受 UPLOAD_CODE/ALLOWED_SUBNET 约束(与上传同类操作)。
九、URL 变换体系(读取面全参数)
变换是 PictShare 的招牌能力:不改文件、只改 URL,服务端在首次访问时惰性生成产物并缓存(同 URL 二次访问直接命中)。变换参数全部拼在文件名之前,可多段叠加(/300x/gray/webp/mrokk1.png 合法,实测返回 image/webp)。
9.1 尺寸变换
| 写法 | 语义 | 实测产物 |
|---|---|---|
/300x200/hash.png | 等比缩放(框内适配,宽为准) | 800×600 → 300×225 |
/300/hash.png | 只给一边,另一边等比 | 800×600 → 200×150(参数为 200 时) |
/300x200/forcesize/hash.png | 强制精确尺寸(裁切) | 300×200 |
实测(file 命令验证产物,实测回显):
curl -s http://192.168.146.128:8131/300x200/mrokk1.png -o r1.png && file r1.png
curl -s http://192.168.146.128:8131/200/mrokk1.png -o r3.png && file r3.png
curl -s http://192.168.146.128:8131/300x200/forcesize/mrokk1.png -o r2.png && file r2.png
r1.png: PNG image data, 300 x 225, 8-bit/color RGBA, non-interlaced
r3.png: PNG image data, 200 x 150, 8-bit/color RGBA, non-interlaced
r2.png: PNG image data, 300 x 200, 8-bit/color RGBA, non-interlaced
💡 技术拓展:
300x200与forcesize对应两种缩放策略,同类策略对比——
策略 原理 取舍 等比缩放(fit) 长边适配、短边留白比,不裁切 不变形,尺寸可能非精确请求值 强制裁切(cover/forcesize) 缩放到覆盖目标框后居中裁切 尺寸精确,边缘内容被裁 拉伸(stretch) 直接改画布 一般不提供,会变形 PictShare 默认等比、
forcesize才裁切;需要精确占位图位(如固定头像框)用 forcesize,普通缩略图用默认等比。
9.2 旋转变换
/left|right|upside/hash.png 三值:left 逆时针 90°、right 顺时针 90°、upside 180°。实测(实测回显):
curl -s http://192.168.146.128:8131/left/mrokk1.png -o r4.png && file r4.png
r4.png: PNG image data, 600 x 800, 8-bit/color RGBA, non-interlaced
800×600 旋转后变 600×800,宽高互换即旋转生效。
9.3 滤镜变换(34 种全表)
滤镜写法 /滤镜名[_参数]/hash.png,带参数滤镜用下划线接值。全表(按官方 IMAGEFILTERS.md 收录,加粗者为 3.7.5 本地实测通过):
| 滤镜 | 参数范围 | 说明 |
|---|---|---|
| pixelate | 1-100 | 马赛克,值越大块越大 |
| blur | 1-6 | 高斯模糊 |
| brightness | -255~255 | 正值提亮、负值压暗 |
| contrast | -100~100 | 负值增对比、正值减对比(GD 语义) |
| colorize | R_G_B 各 -255~255 | 三通道染色,如 colorize_80_20_0 |
| gray | 无 | 灰度 |
| sepia / sepia2 | 无 | 怀旧色调两版 |
| sharpen / emboss | 无 | 锐化 / 浮雕 |
| cool / light / aqua / fuzzy | 无 | 冷色 / 亮调 / 水色 / 毛玻璃 |
| boost / boost2 | 无 | 饱和度增强两版 |
| antique / vintage / retro | 无 | 复古三版 |
| blackwhite | 无 | 黑白(高对比) |
| concentrate / hermajesty / everglow | 无 | 风格滤镜 |
| freshblue / tender / dream | 无 | 风格滤镜 |
| frozen / forest / rain | 无 | 风格滤镜 |
| orangepeel / darken / summer | 无 | 风格滤镜 |
| country / washed | 无 | 风格滤镜 |
实测(实测回显):
curl -s http://192.168.146.128:8131/gray/mrokk1.png -o /dev/null -w '%{http_code} %{content_type}\n'
curl -s http://192.168.146.128:8131/pixelate_10/mrokk1.png -o /dev/null -w '%{http_code} %{content_type}\n'
curl -s http://192.168.146.128:8131/blur_5/mrokk1.png -o /dev/null -w '%{http_code} %{content_type}\n'
curl -s http://192.168.146.128:8131/brightness_80/mrokk1.png -o m1.png && file m1.png
curl -s http://192.168.146.128:8131/contrast_-30/mrokk1.png -o /dev/null -w '%{http_code} %{content_type}\n'
curl -s http://192.168.146.128:8131/colorize_80_20_0/mrokk1.png -o /dev/null -w '%{http_code} %{content_type}\n'
200 image/png
200 image/png
200 image/png
m1.png: PNG image data, 800 x 600, 8-bit/color RGB, non-interlaced
200 image/png
200 image/png
⚠️ 注意:拼错的滤镜名不报错——未知滤镜段被忽略、按原图伺服(实测
/notafilter/mrokk1.png返回 200 + 原图)。验证变换是否生效要靠file/尺寸比对,不能只看状态码。
9.4 格式转换:webp 与 mp4
/webp/hash.jpg:把 JPG/PNG 转 WebP 输出(实测Content-Type: image/webp);/mp4/hash.gif:GIF 动图转 MP4(ffmpeg h264)。注意直链返回的是内嵌播放器 HTML 页(实测Content-Type: text/html; charset=UTF-8),真实视频流要再加/raw;- 视频 URL 缩放:
/320x180/hash.mp4,产物 h264 320×180(同样经/raw取流,见《PictShare 3.7.5 Docker Compose 部署指南》7.3 实测)。
9.5 /raw 后缀与文本仓
/raw 取「无包装的原始流」——视频直链的播放器页、文本仓的渲染页都靠它穿透(实测回显):
# 文本仓:上传 → 渲染页 → /raw 取原文
printf 'hello pictshare\n' | curl -s -F "file=@-" http://192.168.146.128:8131/api/upload
{
"status": "ok",
"hash": "hin0pg.txt",
...
}
curl -s http://192.168.146.128:8131/hin0pg.txt/raw
hello pictshare 1793409654
curl -sI http://192.168.146.128:8131/93i2mx.mp4/raw | grep -iE '^HTTP|content-type'
HTTP/1.1 200 OK
Content-Type: video/mp4
9.6 动态图像:identicon 与 placeholder
两个不依赖已存文件的特殊控制器(实测回显):
curl -s http://192.168.146.128:8131/identicon/team-alice -o /dev/null -w '%{http_code} %{content_type}\n'
curl -s http://192.168.146.128:8131/placeholder/555x250/Hello -o p.png && file p.png
200 image/svg+xml
p.png: JPEG image data, ... 555x250, components 3
/identicon/<字符串>:按字符串确定性生成 SVG 识别图(同串同图),适合用户头像兜底;/placeholder/<宽>x<高>/<文字>:按尺寸生成 JPEG 占位图(GD 生成,质量取 JPEG_COMPRESSION 默认 90)。
9.7 直传变换:POST /api/passthrough/image/{变换段}
上传时就拿到变换产物而不落原图(multipart 的 file + URL 路径携带变换段,响应直接是图像字节流)。实测(实测回显):
curl -s -F "file=@test.png" "http://192.168.146.128:8131/api/passthrough/image/60x100/forcesize" -o r15.png && file r15.png
r15.png: PNG image data, 60 x 100, 8-bit/color RGBA, non-interlaced
变换段语法与第九章读取面完全一致(尺寸/forcesize/滤镜/rotate/webp),缺 file 字段返回 400 + {"status":"err","reason":"No file uploaded"}。适合截图脚本一步出缩略图的流水线。
9.8 变换产物的存储形态
每个变换产物以 前缀_hash.png 形态缓存在原 hash 目录旁(如 300_mrokk1.png、md5 前缀_原名),同参数再次访问零开销;原文件与产物都不会被变换请求修改。产物数量无上限(MAX_RESIZED_IMAGES 未实现),高变异场景注意磁盘增长。
十、MCP 端点:POST /mcp
PictShare 3.x 内置 MCP(Model Context Protocol)服务器,LLM 代理可把实例当工具箱调用。端点为 JSON-RPC over HTTP,除 initialize 外的所有请求必须带 Mcp-Session-Id 头。
💡 技术拓展:MCP 是 Anthropic 2024 年开放的「LLM 工具接入协议」。同类接入方式对比——
方式 形态 适用 MCP 标准化 JSON-RPC,工具自描述(tools/list) Claude Desktop/Claude Code 等 Agent 生态 自定义 function calling 各厂商私有 schema 单一厂商生态内 OpenAPI 插件 以 OpenAPI 文档描述 REST API 通用但缺乏会话与资源语义 PictShare 选择 MCP 使图床能力可被 Agent 直接编排(上传截图→取 hash→建相册→给外链);本文实测其握手与调用全程。
10.1 握手与工具清单(实测回显)
curl -s -X POST http://192.168.146.128:8131/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"local","version":"1.0"}}}'
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"logging":{},"completions":{},"tools":{}},"serverInfo":{"name":"PictShare","version":"1.0.0"},"instructions":"Self-hosted media hosting. Upload files by URL or base64, then use the returned hash with the info\/transform\/album\/delete tools."}}
响应头里的 Mcp-Session-Id(实测值形如 1499ef5e-0ae6-4ca3-b90f-994c1526be4f)需在后续请求回传。tools/list 返回 7 个工具(实测回显节选,保留全部工具名与 schema 要点):
upload_from_url(url) # 公网 URL 抓取入库(max 20 MB),返回 hash/url/delete_code
upload_base64(data) # base64/数据 URI 入库
upload_html(html, html_upload_code) # HTML 页托管(默认禁用,需 HTML_HOSTING_ENABLED + 独立口令)
get_file_info(hash) # 元数据:mime/size/sha1/upload time
delete_file(hash, delete_code) # 凭删除码永久删除
create_album(hashes[]) # 相册(max 200)
transform_image(hash, size, filter, rotate, forcesize, webp)
# 拼装变换 URL,首访惰性渲染
10.2 工具调用与会话校验(实测回显)
curl -s -X POST http://192.168.146.128:8131/mcp \
-H 'Content-Type: application/json' -H 'Mcp-Session-Id: <上一步返回的会话ID>' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_file_info","arguments":{"hash":"mrokk1.png"}}}'
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\n \"mime\": \"image\/png\",\n \"size\": 3295,\n \"size_human\": \"3.22 KB\",\n \"hash\": \"mrokk1.png\",\n \"sha1\": \"6fa85e1c71824d69849faf6d8ea5fb8bb21d0e86\",\n \"uploaded\": 1793352621\n}"}],"isError":false,"structuredContent":{"mime":"image\/png","size":3295,"size_human":"3.22 KB","hash":"mrokk1.png","sha1":"6fa85e1c71824d69849faf6d8ea5fb8bb21d0e86","uploaded":1793352621}}}
不带会话 ID 的调用被拒(实测回显):
{"jsonrpc":"2.0","id":"","error":{"code":-32600,"message":"A valid session id is REQUIRED for non-initialize requests."}}
十一、错误码速查与排障实录
11.1 全接口错误码速查
| reason 原文 | 端点 | 触发条件(实测) |
|---|---|---|
No file uploaded | upload/passthrough | 缺 file/base64/url 字段 |
Incorrect upload code specified - Access denied | upload/album | UPLOAD_CODE 校验失败 |
Access denied | upload/album | 来源 IP 不在 ALLOWED_SUBNET |
Unsupported mime type: <type> | upload | CONTENTCONTROLLERS 白名单拒绝 |
Private IP range | upload?url= | SSRF 防护:目标私网 |
Invalid URL | upload?url= | 非 http(s) 目标 |
Missing hash | info | URL 未带 hash |
Hash not found | info/delete | hash 不存在或已删除 |
Missing code or hash | delete | 路径缺删除码或 hash 段 |
Invalid delete code | delete | 删除码不符 |
Missing or empty hashes array | album | 缺 hashes[] |
Hash not found: <h> | album | 成员 hash 不存在 |
Too many hashes. Maximum 200 per album | album | 超上限(源码校验) |
Invalid hash value | album | 成员非法字符串 |
Unknown API call + hint | 全部 /api/* | 端点不存在(含已移除的 v2 老端点) |
-32600 A valid session id is REQUIRED... | /mcp | 缺 Mcp-Session-Id 头 |
11.2 排障实录
实录一:老接口 404 化为 Unknown API call。 现象:按官方 API.md 调 api/pastebin.php 返回 Unknown API call。定位:v3 重构删除了全部 *.php RPC 端点(5.6 逐个实测)。解决:迁移到 3.x 形态(/api/upload + base64/url=/file)。
实录二:删除成功但外链仍 200。 现象:/api/delete 返回 ok,外链访问仍 HTTP 200。定位:该文件删除前被访问过,Redis cache:byurl 键残留(7.3 对照实验:未访问过的文件删除后 404;残留 200 的响应体为 1 字节空内容)。解决:探测判据改用 /api/info/<hash>;等待缓存过期或重启实例清除。
实录三:变换 URL 拼错字母「静默原图」。 现象:/gre/mrokk1.png 想加灰度滤镜,返回 200 但图没变。定位:未知滤镜段被忽略、按原图伺服(9.3 实测 notafilter 同样原图返回),接口不报错。解决:用 file 命令比对产物尺寸/类型,或固定用 9.3 全表内的滤镜名。
实录四:GIF 转 MP4 拿到的是网页。 现象:/mp4/xxx.gif 返回 text/html。定位:3.x 对视频类直链统一返回内嵌播放器页,原始流在 /raw 后缀(9.4/9.5 实测)。解决:程序取流一律加 /raw;网页场景保留播放器页即可。
十二、常见问题(FAQ)
Q1:上传返回 duplicate:true,我的文件没传上去吗?
传上了——内容已存在(SHA1 相同),服务器返回已有 hash 且不生成新删除码。想拿新删除码只能用新文件名+微调内容(哪怕改 1 个字节)。
Q2:为什么 /300x200/ 出来的图不是精确 300×200?
默认等比缩放保证不变形;要精确尺寸在路径里加 forcesize(9.1 对照实测)。
Q3:删除码丢了怎么办?
无法补发(只在上传响应出现一次)。管理员可用 MASTER_DELETE_CODE 或 /admin 后台删除兜底;否则文件将永久留存。
Q4:怎么在删除后确认真的删掉了?
唯一可靠判据 curl /api/info/<hash> 返回 Hash not found;外链状态码受缓存残留影响不可信(7.3、实录二)。
Q5:上传口令能不能放进 URL?
可以——uploadcode 支持 GET 查询参数(/api/upload?uploadcode=xxx)与 POST 字段两种形态;URL 形态会进访问日志,介意留痕就用 POST。
Q6:Agent 接入用哪套接口?
有 MCP 客户端(Claude Desktop/Claude Code 等)直接走 /mcp(第十章,7 个工具自描述);脚本集成走 /api/*;两者共用同一份存储与删除码体系。
十三、参考链接
- 官方 API 手册:https://github.com/HaschekSolutions/PictShare/blob/master/rtfm/API.md(注意其仍记载已移除的 v2 端点,以本文实测为准)
- 官方变换手册:https://github.com/HaschekSolutions/PictShare/blob/master/rtfm/MODIFIERS.md 与 IMAGEFILTERS.md
- 官方 MCP 说明:https://github.com/HaschekSolutions/PictShare/blob/master/rtfm/MCP.md
- 官方仓库:https://github.com/HaschekSolutions/PictShare(最新稳定版 v3.7.5,2026-10-27 发布,查询日期 2026-10-31)
- 本专栏系列:《PictShare 介绍与选型指南》、《PictShare 3.7.5 Docker Compose 部署指南》、《PictShare 3.7.5 配置参数详解》。