# PicBed **Repository Path**: asuhu/picbed ## Basic Information - **Project Name**: PicBed - **Description**: 一套完全不依赖数据库的 PHP 图床程序,简单图床程序,便捷图床。所有数据(用户、图片元信息、操作日志)都以 JSON 文件存储在 `data/` 目录下,通过文件锁(`flock`)与原子写入保证并发安全。把整个目录丢到任意支持 PHP 7.4+ 的虚拟主机即可运行,无需 MySQL / SQLite。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-10 - **Last Updated**: 2026-06-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 图床 · PicBed(无数据库版) 一个**不依赖任何数据库**的自托管图床 / 图片外链系统。解压上传即用,数据以带安全守卫的 JSON 文件保存;支持多用户与细粒度权限、私密/加密/限时分享、回收站、秒传去重、存储配额、RESTful API,并可在**后台页面可视化**地把图片改存到阿里云 OSS / 腾讯云 COS / MinIO(S3 兼容)。 > 默认管理员:`admin` / `admin888` —— **部署后请立即在「后台 → 我的账户 → 修改密码」中更改**。 --- ## 目录 - [为什么不用数据库](#为什么不用数据库) - [功能一览](#功能一览) - [安装](#安装) - [Web 服务器安全配置(必读)](#web-服务器安全配置必读) - [配置项](#配置项site_config_b8php) - [系统设置(后台可视化)](#系统设置后台可视化) - [对接对象存储 OSS / COS / MinIO](#对接对象存储-oss--cos--minio) - [存储配额](#存储配额) - [回收站 · 限时分享 · 高级筛选 · 文件安全](#回收站--限时分享--高级筛选--文件安全) - [RESTful API](#restful-api) - [**WordPress 详细对接**](#wordpress-详细对接) - [私密 / 加密图片外链说明](#私密--加密图片外链说明) - [数据备份与迁移](#数据备份与迁移) - [登录失败排查](#登录失败排查) - [安全性](#安全性) - [杀毒软件误报说明](#杀毒软件误报说明) --- ## 为什么不用数据库 图床的数据模型很简单(图片元信息 + 用户 + 日志),并发量也不高。用扁平 JSON 文件 + 文件锁(`flock`)+ 原子写(临时文件→`rename`)即可保证一致性,免去安装、配置、备份数据库的全部负担: - **零依赖**:只要 PHP 7.4+,无需 MySQL / SQLite。备份就是打包一个 `data/` 目录。 - **可靠写入**:每次修改都在排他锁内「读→改→写」,临时文件落地后原子替换,断电也不会读到半截数据。 - **够用的规模**:个人与小团队场景完全胜任(容量评估见下)。 **适用规模**:图片数量是主要瓶颈(每次增删要整体读写 `images.php`),用户数几乎不是。经验值:**1 万张图片以内体验顺滑;1–5 万张可用但相册页渐慢;超过 5 万张建议上对象存储 + 适度分页**。 --- ## 功能一览 - **多种上传**:点击 / 拖拽 / 粘贴 / 批量,支持远程 URL 抓取(已做 SSRF 防护)。 - **原图不压缩**:原样存储,外链即原始字节。 - **多格式外链一键复制**:URL / Markdown / HTML / BBCode / 纯文本。 - **可见性控制**:公开 / 私密(仅自己)/ 加密(密码访问)。 - **细粒度权限**:上传、删除自己的、删除任何人的、创建私密图、查看外链、查看日志、管理用户 —— 共 7 项,按用户分配。 - **高级筛选**:按文件名、标签、上传时间区间、文件大小区间、可见性、归属组合过滤。 - **回收站**:删除先进回收站(默认保留 30 天),可恢复 / 彻底删除,超期自动清理。 - **限时分享**:上传时可设 1 / 7 / 30 / 90 天有效期,过期链接自动失效(返回 410)。 - **文件安全检测**:校验真实图片 + 文件头 Magic Number + 脚本特征扫描,拦截伪装成图片的木马 / WebShell。 - **秒传去重**:按内容哈希(SHA-256)去重,重复图片直接复用已有链接。 - **存储配额**:全局总配额 + 每用户配额,原子校验防并发绕过。 - **对象存储(可选)**:阿里云 OSS / 腾讯云 COS / MinIO(AWS S3 兼容),**后台页面可视化配置**,公开图走对象存储 / CDN 直链省带宽。 - **操作日志**:登录、上传、删除、改密、设置变更等全程记录,按月分文件。 - **RESTful API**:令牌鉴权,便于 WordPress / CMS / 脚本集成。 - **安全加固**:CSRF 全覆盖、登录爆破节流、会话固定防护、数据文件守卫、敏感目录禁直取。 --- ## 安装 1. 把整个目录上传到网站可访问的位置(如 `/var/www/picbed`)。 2. 确保 PHP 进程对 `data/`、`upload/`、`private/` 三个目录有**写权限**: ```bash chown -R www-data:www-data data upload private chmod -R 755 data upload private ``` 3. 浏览器打开站点,用 `admin` / `admin888` 登录,**立即修改密码**。 4. 按下一节配置好 Web 服务器安全规则。 5. (可选)部署完成后删除根目录的 `check.php` 环境自检页。 环境要求:**PHP 7.4+**(推荐 8.x);需要 `gd` 扩展(读取图片尺寸)。启用对象存储时需要 `curl` 扩展。 --- ## Web 服务器安全配置(必读) `data/`、`private/` **绝不能**被公网直接访问。程序已自带 Apache 用的 `.htaccess`,但 **Nginx 不读取 `.htaccess`**,必须在站点配置中手动添加: ```nginx server { # ... 你的现有配置 ... # 禁止直接访问数据与私密目录 location ~ ^/(data|private)/ { deny all; return 403; } # 上传目录禁止执行任何脚本(防止上传木马被解析) location ~ ^/upload/.*\.(php|php5|phtml|pl|py|sh|cgi)$ { deny all; return 403; } # 禁止从 HTTP 直接获取配置文件 location = /site_config_B8.php { deny all; return 403; } } ``` Apache 用户确保站点开启了 `AllowOverride All` 使 `.htaccess` 生效即可。 > 即便上述规则被遗漏,`data/` 下的数据文件本身也带 ``(或退而求其次用参数 `token=<令牌>`)。接口权限与该用户的权限一致。 **路由**:`api.php/<资源>` 或 `api.php?action=<资源>` 均可。 | 方法 | 资源 | 说明 | | --- | --- | --- | | GET | `ping` | 测试令牌,返回用户名与权限 | | POST | `upload` | 上传:字段 `file`(可多选 `file[]`)或 `url=`(远程);可选 `visibility`=public/private/password、`password`、`tags`(逗号分隔)、`expire_days` | | GET | `list` | 列出图片;参数 `page`、`per`(≤100)、`vis`、`scope`(管理员可 `all`)、`q`、`tag`、`from`、`to`、`min`、`max` | | GET | `info?id=` | 查询单张信息与外链 | | POST/DELETE | `delete?id=` | 删除图片(移入回收站) | | GET | `trash` | 列出回收站中的图片 | | POST | `restore?id=` | 从回收站恢复 | | POST/DELETE | `purge?id=` | 彻底删除 | | GET | `quota` | 查询已用 / 配额、全站占用 | 返回统一 JSON:成功 `{"ok":true,...}`,失败 `{"ok":false,"msg":"..."}`。 **上传返回结构**(关键字段):上传成功时顶层附带 `url` / `links` / `image` 便于直接取用,同时 `results[]` 给出每个文件的明细: ```json { "ok": true, "url": "https://你的域名/upload/202606/xxxx.png", "links": { "url": "https://你的域名/upload/202606/xxxx.png", "html": "\"...\"", "markdown": "![...](...)", "bbcode": "[img]...[/img]", "text": "https://..." }, "image": { "id": "c6dd9ceb918240f8", "url": "...", "size": 69, "width": 1, "height": 1, "visibility": "public", "tags": ["demo"], "...": "..." }, "results": [ { "ok": true, "duplicate": false, "image": { "...": "..." }, "links": { "...": "..." } } ] } ``` **curl 上传示例** ```bash curl -X POST https://你的域名/api.php/upload \ -H "Authorization: Bearer pb_你的令牌" \ -F "file=@/path/to/photo.jpg" \ -F "visibility=public" ``` **远程抓取示例** ```bash curl -X POST "https://你的域名/api.php?action=upload" \ -H "Authorization: Bearer pb_你的令牌" \ --data-urlencode "url=https://example.com/pic.png" ``` > 安全建议:对外开放 API 时全程使用 HTTPS;令牌一旦泄露请立即在后台重新生成(旧令牌随即失效)。 --- ## WordPress 详细对接 把 PicBed 作为 WordPress 的图片外链 / 媒体卸载后端,有几条路线,按需选择。 ### 准备工作(所有方式都需要) 1. 在 PicBed 后台为对接专用账号生成 API 令牌:**后台 → 我的账户 → API 令牌 → 生成**,复制形如 `pb_xxxx` 的令牌。 - 建议**单独建一个只有「上传 / 查看外链」权限的账号**给 WordPress 用,而不是用管理员账号,降低令牌泄露风险。 2. 确保 PicBed 站点启用 **HTTPS**。 3. 记下 PicBed 站点地址,如 `https://img.example.com`。 --- ### 方式一:迷你插件——上传到媒体库时自动转存到 PicBed(推荐) 新建文件 `wp-content/plugins/picbed-offload/picbed-offload.php`,粘贴以下代码,然后在 WordPress 后台「插件」里启用,并在「设置 → PicBed 图床」填入站点地址与令牌。 它的作用:**每当你向媒体库上传图片,插件会把图片转存到 PicBed,并把该附件在文章中引用的 URL 替换为 PicBed 外链**(节省 WordPress 主机带宽与空间)。 ```php

PicBed 图床

PicBed 站点地址
API 令牌
true, CURLOPT_TIMEOUT => 60, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"], CURLOPT_POSTFIELDS => [ 'file' => new CURLFile($file_path), 'visibility' => 'public', ], ]); $raw = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code !== 200) return null; $res = json_decode($raw, true); // 顶层 url 便是公开外链(批量时取 results[0].image.url) return (!empty($res['ok']) && !empty($res['url'])) ? $res['url'] : null; } ``` 启用后:进入「设置 → PicBed 图床」填好地址与令牌,之后**正常上传图片到媒体库即可**,文章里引用的就是 PicBed 外链。 > 说明:本插件复用 WordPress 自带的媒体上传流程做转存,不改动你的写作习惯。若上传失败(网络 / 令牌问题),会回退使用 WordPress 本地地址,不影响发文。 --- ### 方式二:用 `wp_remote_post` 按需推送(不改媒体库行为) 如果你不想改媒体库行为,只想在需要时把某个本地文件推到 PicBed 并拿回外链,用一个辅助函数即可(放进主题 `functions.php` 或任何插件)。这种方式**不依赖 curl**(用 WordPress 内置 HTTP API),适合托管环境受限的场景: ```php /** * 把本地文件上传到 PicBed,返回公开外链;失败返回 WP_Error。 * @param string $file_path 服务器上的图片绝对路径 * @return string|WP_Error */ function my_picbed_upload($file_path) { $base = 'https://img.example.com'; // 你的 PicBed 地址 $token = 'pb_你的令牌'; if (!file_exists($file_path)) return new WP_Error('no_file', '文件不存在'); // 手动构造 multipart/form-data $boundary = wp_generate_password(24, false); $body = ''; $body .= "--$boundary\r\n"; $body .= 'Content-Disposition: form-data; name="visibility"' . "\r\n\r\npublic\r\n"; $body .= "--$boundary\r\n"; $body .= 'Content-Disposition: form-data; name="file"; filename="' . basename($file_path) . '"' . "\r\n"; $body .= 'Content-Type: ' . mime_content_type($file_path) . "\r\n\r\n"; $body .= file_get_contents($file_path) . "\r\n"; $body .= "--$boundary--\r\n"; $resp = wp_remote_post("$base/api.php/upload", [ 'timeout' => 60, 'headers' => [ 'Authorization' => "Bearer $token", 'Content-Type' => "multipart/form-data; boundary=$boundary", ], 'body' => $body, ]); if (is_wp_error($resp)) return $resp; $data = json_decode(wp_remote_retrieve_body($resp), true); if (empty($data['ok']) || empty($data['url'])) { return new WP_Error('picbed_fail', $data['msg'] ?? '上传失败'); } return $data['url']; } // 用法: // $url = my_picbed_upload('/var/www/uploads/photo.jpg'); // if (!is_wp_error($url)) echo '外链:' . esc_url($url); ``` --- ### 方式三:远程图片入库(采集 / 搬运场景) 如果要搬运的是**远程图片 URL**(例如采集文章里的外站图片),可以让 PicBed 直接抓取,无需先下载到本地: ```php function my_picbed_grab($remote_url) { $base = 'https://img.example.com'; $token = 'pb_你的令牌'; $resp = wp_remote_post("$base/api.php?action=upload", [ 'timeout' => 60, 'headers' => ['Authorization' => "Bearer $token"], 'body' => ['url' => $remote_url, 'visibility' => 'public'], ]); if (is_wp_error($resp)) return $resp; $data = json_decode(wp_remote_retrieve_body($resp), true); return (!empty($data['ok']) && !empty($data['url'])) ? $data['url'] : new WP_Error('fail', $data['msg'] ?? '抓取失败'); } ``` > PicBed 的远程抓取已做 SSRF 防护(拒绝内网 / 保留地址、禁跟随重定向、限定 http/https),可安全用于不可信 URL。 --- ### WordPress 对接常见问题 - **上传后文章里仍是本地图?** 方式一依赖 `wp_get_attachment_url` 过滤器;部分主题 / 页面构建器直接硬编码了 URL,可改用方式二在你自己的流程里显式替换。 - **令牌权限**:给 WordPress 用的账号至少需要「上传」与「查看外链」权限;若要程序化删除,再加「删除自己的图片」。 - **私密图不可热链**:WordPress 嵌入文章的图片必须是**公开**图(`visibility=public`),否则外链指向 `view.php` 需鉴权,前台访客看不到。上面示例都已固定 `visibility=public`。 - **大图超时**:大文件可调高 `CURLOPT_TIMEOUT` / `wp_remote_post` 的 `timeout`,并确认 PicBed 的 `max_size` 足够大。 --- ## 私密 / 加密图片外链说明 - **公开图**:外链是直连文件(或对象存储 / CDN)地址,速度最快、可在任意网站热链,所有格式开箱即用。 - **私密 / 加密 / 限时图**:出于安全考虑,外链指向 `view.php?id=...`,需要登录(私密)、输入密码(加密)或在有效期内(限时)才能查看,**不可匿名热链**。这是隐私与可嵌入性之间的必要取舍。 --- ## 数据备份与迁移 整个系统的状态就在三个目录里,**打包即备份**: - `data/` — 用户、图片元信息、日志、设置覆盖(含对象存储密钥)。 - `upload/` — 本地存储的公开图原文件。 - `private/` — 本地存储的私密 / 加密 / 限时图原文件。 迁移到新服务器:把这三个目录连同程序文件一起复制过去即可,无需导入导出数据库。若已启用对象存储,图片本体在云端,这三个目录里只有元信息和少量本地遗留图。 > 备份包含 `data/settings.php` 时请妥善保管 —— 其中可能含有对象存储密钥(虽然文件带守卫,但备份件在你自己手里要注意保密)。 --- ## 登录失败排查 最常见原因是 **`data/` 目录不可写**,导致建不出管理员账号,登录时表现为「密码错误」。程序已内置环境自检,登录页会直接提示。手动排查: ```bash # 确认 data/ 可被 PHP 进程写入 ls -ld data chown -R www-data:www-data data upload private chmod -R 755 data upload private ``` 也可访问 `check.php` 查看环境诊断(用完请删除)。若你改过 `default_admin` 又已生成过用户文件,新默认管理员不会自动覆盖已有账号。 --- ## 安全性 已实现并经攻击模拟验证的防护: - **数据文件守卫**:`data/` 下用户、图片、日志、设置、节流等数据均以 `*.php` 存储,文件开头带 `` 守卫。即便 Web 服务器误配置、把它们当静态资源或直接以 PHP 执行,也只会立即 403 退出、**绝不输出明文**(密码哈希、对象存储密钥等);程序内部读取走文件系统不受影响。旧版 `*.json` 会在首次启动时自动迁移。 - **敏感目录禁直取**:`data/`、`private/` 由 `.htaccess`(Apache)/ Nginx 规则拒绝;配置文件 `site_config_B8.php` 同样禁止 HTTP 直取。 - **后台与配置文件改名**:后台为 `safe_admin_B8.php`、配置为 `site_config_B8.php`,降低被自动化扫描器命中的概率(纵深防御,非唯一防线)。 - **CSRF 全覆盖**:所有改状态的表单 / AJAX 都校验令牌。 - **登录爆破节流**:同一 IP 连续失败达阈值后锁定一段时间,锁定期内正确密码也拒绝。 - **会话固定防护**:登录成功后 `session_regenerate_id`;HTTPS 下自动加 `Secure` Cookie。 - **权限与越权防护**:7 项细粒度权限;仅超级管理员可授予管理员 / 管理用户权限、可操作管理员账户;保留至少一名管理员。 - **上传安全**:真实图片校验 + Magic Number + 脚本特征扫描 + 强制图片扩展名 + 随机文件名 + 上传目录禁脚本执行 + 文件名取 basename。 - **SSRF 防护**:远程抓取拒绝内网 / 保留 / 云元数据地址,禁跟随重定向,限定 http/https,按 IP 钉死防 DNS rebinding。 - **配额并发安全**:上传在锁内做去重 → 配额 → 写入,失败回滚,不留孤儿文件。 - **API 安全**:令牌以哈希存储、`hash_equals` 比较;输出不含密码哈希;跨用户访问受归属校验。 - **限时链接**:过期返回 410;受控图经 `view.php` 鉴权,私有对象由服务端签名取回。 > 注:后台 / 配置改名属于「降低被扫描命中率」的纵深防御措施,真正的访问控制由登录、权限、CSRF 等保证。请务必尽快修改默认密码并全程使用 HTTPS。 --- ## 杀毒软件误报说明 个别杀毒软件(如部分国产安全软件)可能对图床类程序中**远程抓取**相关代码产生误报(例如标记为 HackTool)。本项目已做处理: - 远程抓取代码集中在独立文件 `lib/fetch.php`,对象存储代码在 `lib/storage.php`,均**不使用原始套接字**(无 `socket_create` 等易触发特征的写法),只用 curl。 - 若仍被误报,可在杀软中把项目目录加入白名单;若你**不使用远程抓取**功能,可直接删除 `lib/fetch.php`;若**不使用对象存储**,可删除 `lib/storage.php`。删除后对应功能关闭,其余不受影响。 这类提示是静态特征误报,并非程序存在后门。如仍不放心,欢迎自行审阅这两个文件的源码(代码量很小、注释完整)。