ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

帝国CMS 7.5图片附件同步七牛云插件开发与迁移实践

帝国CMS 7.5图片附件同步七牛云插件开发与迁移实践 简介基于PHP的帝国CMS7.5七牛云图片附件同步插件主要面向帝国CMS站点管理员与PHP开发者解决图片附件在本地服务器存储压力大、访问延迟高的问题通过七牛云将图片上传、同步、删除操作自动化并借助CDN加速内容分发。压缩包共23个文件包括22个PHP源码文件和1份txt使用说明整体仅35KBPHP文件按功能划分包含图片同步、删除检测、配置管理、七牛云API请求封装等模块txt文件提供部署与配置说明便于快速上手。目前已有316人学习适合需要优化帝国CMS附件管理或研究PHP云存储对接的开发者。插件完整覆盖图片自动同步、快速上传、智能删除、CDN加速分发与安全备份等核心功能代码可直接运行于帝国CMS7.5环境。实现中重点应用了PHP文件操作函数、curl库HTTP请求、json_decode响应处理、try-catch异常捕获及AccessKey/SecretKey敏感配置管理并封装了通过curl上传图片、调用七牛云接口删除远端文件等具体流程展现出七牛云API对接的完整技术路径。这份资源既可作为生产环境中的附件云化管理工具也可作为学习PHP云存储集成、帝国CMS二次开发的实用范本。1. 帝国CMS 7.5 的图片越来越多插件到底帮你解决什么对于一个日更几条、每篇配几张图片的资讯站跑上三个月服务器上/d/file目录里就会攒出数万个小图片文件。帝国CMSECMS7.5 默认把图片放在本地磁盘文件路径写进phome_enewsfile表正文里的img标签则直接引用/d/file/...这样相对路径。本地磁盘消耗大、备份慢页面加载时 PHP 进程和带宽同时被拖住是这类站点最常见的容量瓶颈。七牛云图片附件同步、上传删除插件要解决的就是三个问题新上传的图片自动推到七牛空间删除文章或附件时连云端对象一起清除存量图片按目录批量迁移上云。站在 PHP 开发者的角度看这套插件的本质只在 ECMS 原有的文件存盘动作前后多做一次对象存储的调用并把数据库里对应的路径或 URL 做一致性替换不破坏 ECMS 自身的模板和内容结构。2. 对接七牛前先理清 ECMS 附件数据、空间映射与 PHP SDK 初始化很多人在写这个插件时栽的第一个跟头不是七牛 SDK 不会用而是没搞清filepath字段里存的值到底长什么样。ECMS 在不同操作路径下phome_enewsfile.filepath可能存成d/file/日期/文件名、../d/file/日期/文件名或者/d/file/日期/文件名而内容表里的img src又是另一套写法。如果一开始就写死某个前缀做字符串替换后面部署到别的服务器时会发现大量图片路径对不上。所以在写上传和删除逻辑之前先把 ECMS 的附件数据结构和七牛空间的 key 设计对齐后面所有代码才有统一的处理基础。2.1 phome_enewsfile 表里的 filepath 是插件的起点ECMS 7.5 的附件记录存放在phome_enewsfile表最常见的几个字段包括附件 ID、原始文件名、文件大小、上传时间、所属栏目 ID以及最重要的存储路径字段filepath。上传图片时ECMS 会把文件移动到站点附件目录随后向这张表插入一条记录并把相对路径写入filepath。正文编辑器里的img src则直接指向这个相对路径对应的 URL。插件在做同步与删除时都要以这条记录作为依据上传后拿到filepath才能算出七牛空间的 key删除前读到filepath才知道要清理云端哪个对象。所以代码里第一件事是写一个统一的标准函数把各种形态的filepath清洗成纯粹的相对路径。常见的做法是用正则去掉开头的../、./和绝对路径前缀只保留d/file/之后的部分作为七牛 key。function normalizeKey($filepath) { // 统一把反斜杠转成正斜杠兼容 Windows 环境下的路径写法 $filepath str_replace(\\, /, $filepath); // 匹配 d/file/ 之后的所有内容作为 key兼容带权限目录的写法 if (preg_match(#(?:^|/)d/file/?(.)$#, $filepath, $m)) { return $m[1]; } // 不满足 d/file 规则时直接去掉开头的斜杠 return ltrim($filepath, /); }这段代码解决的是路径形态不一致的问题。preg_match里的(?:^|/)表示匹配起始位置或目录分隔符d/file/?保证了目录名后可有可无的斜杠(.)$捕获真正要存入七牛的 key。没有用简单的str_replace(d/file/, , $filepath)是因为该函数无法处理../d/file/、/d/file/两种前缀并存的情况而正则一次匹配就能覆盖全部路径变体。后续上传、删除、迁移都复用这一个函数避免各模块各写一套清洗逻辑。2.2 空间内 key 的设计与本地路径的映射key 是对象在七牛空间里的唯一标识等同于传统文件系统里的路径。ECMS 的附件本来就是按日期目录存放的直接把d/file/之后的路径作为 key是最省事也最可读的方案。映射关系如下表数据库 filepath七牛空间 key公网访问 URLd/file/2024/08/01/news001.jpg2024/08/01/news001.jpghttps://img.example.cn/2024/08/01/news001.jpg../d/file/2024/08/01/news001.jpg2024/08/01/news001.jpghttps://img.example.cn/2024/08/01/news001.jpg/d/file/2024/08/01/news001.jpg2024/08/01/news001.jpghttps://img.example.cn/2024/08/01/news001.jpg表格里三种形态的 filepath 经过normalizeKey()处理得到的 key 完全一致。这个设计的好处是迁移存量数据时不需要回写数据库路径只需要在上传返回 URL 时按同一规则拼接。如果你的服务器要在一个七牛空间里承载多个站点的图片可以在 key 前加一个业务前缀比如ecmsa/2024/08/01/news001.jpg这样同桶内不同站点互不干扰。但要注意加了前缀后删除和查询逻辑里也要同步拼接不能只在 upload 时加而 delete 时忘了。2.3 用 Composer 引入七牛 PHP SDK 并拿到第一个上传凭证七牛官方提供了 PHP SDK包名是qiniu/php-sdk标准做法是在项目根目录执行 Composer 安装。ECMS 7.5 站点通常跑在 PHP 5.4 到 PHP 7.4 之间qiniu/php-sdk 的 7.x 版本对这些版本的支持比较成熟如果服务器还停留在 PHP 5.2Composer 装不了可以直接把 SDK 的 vendor 目录解压到插件包内用 require 引入。cd /www/wwwroot/example.com php composer.phar require qiniu/php-sdk安装完成后写一个最简单的 PHP 片段验证 AK/SK 和空间连通性。AccessKey 和 SecretKey 在七牛控制台的“密钥管理”页面获取bucket 填空间名称注意不是空间绑定的访问域名。?php require __DIR__ . /vendor/autoload.php; use Qiniu\Auth; $accessKey 你的AccessKey; $secretKey 你的SecretKey; $bucket 你的空间名; $auth new Auth($accessKey, $secretKey); $token $auth-uploadToken($bucket, null, 3600); echo $token;运行后如果输出一段以空格分隔的长字符串说明 AK/SK 鉴权已通过可以生成上传凭证。uploadToken()的第一个参数是空间名第二个参数在不指定 key 时传 null表示生成可上传任意 key 的凭证第三个参数 3600 是令牌有效期单位是秒生产环境建议设置成 2400 至 3600 之间避免凭证长时间暴露在浏览器端。3. 上传链路改造让新发布的图片直接推进七牛把 SDK 接入之后下一步就是改上传链路。理解这条链路之前先明确一个原则本地文件仍然照常落盘七牛推送是同步追加的动作不做“跳过本地、只传七牛”的激进改造。原因很简单ECMS 后台的图片编辑、缩略图生成、删除操作都依赖本地路径一旦本地缺失后台会先报错。插件要做的是在上传流程中额外推送一份到七牛并把返回的云端 URL 替换到正文输出里。3.1 在 ECMS 上传流程里插入改写点ECMS 里图片上传的入口分成两类一类是前台投稿编辑器一类是后台内容管理的文件上传接口。两条路径最终都会把临时文件移动到站点附件目录并调用数据库写入函数记录附件信息。插件常见的接入方式是在文件移动成功之后、数据库写入完成之前插入一段“推送到七牛”的逻辑。上传入口修改位置接入方式前台投稿编辑器处理$_FILES的表单脚本文件 move 后调用同步类后台内容编辑图片上传后台文件上传处理页文件 move 后调用同步类后台附件管理“新增附件”附件管理接口与编辑器路径共用同一封装类实际改造中不需要每个入口单独写一份上传代码而是封装一个统一的QiniuStorage类各入口只调一个方法。这样后续如果换成阿里云 OSS 或腾讯云 COS只需替换一个类的内部实现。// 改造前ECMS 原有逻辑移动临时文件到附件目录 move_uploaded_file($_FILES[file][tmp_name], $targetPath); // 改造后移动完成后追加七牛推送 move_uploaded_file($_FILES[file][tmp_name], $targetPath); QiniuStorage::getInstance()-pushFile($targetPath);这里不删除原来的move_uploaded_file也不改动$targetPath的生成逻辑。pushFile()内部调用七牛 SDK 上传同一文件成功则返回云端 URL失败则记录日志并抛出异常。注意异常不要直接中断发布流程否则七牛临时故障时会导致整篇内容发布失败网站编辑人员会误以为系统坏了。实际项目里的做法是捕获异常后降级为“仅本地存储”并在日志和后台提示里标黄便于运维发现。3.2 统一封装 QiniuStorage落盘、推送、URL 生成一次完成封装类里包含三个核心方法上传文件、删除对象、从filepath还原云端 URL。关键点在于上传时上传凭证要指定 key这样七牛空间里的对象路径是可控的删除时才找得到对应资源。?php require __DIR__ . /vendor/autoload.php; use Qiniu\Auth; use Qiniu\Storage\BucketManager; use Qiniu\Storage\UploadManager; class QiniuStorage { private $auth; private $bucket; private $domain; public static function getInstance($accessKey, $secretKey, $bucket, $domain) { static $instance null; if ($instance null) { $auth new Auth($accessKey, $secretKey); $instance new self($auth, $bucket, $domain); } return $instance; } public function __construct($auth, $bucket, $domain) { $this-auth $auth; $this-bucket $bucket; $this-domain rtrim($domain, /); } public function pushFile($filepath) { $key normalizeKey($filepath); $token $this-auth-uploadToken($this-bucket, $key, 3600); $uploadMgr new UploadManager(); list($ret, $err) $uploadMgr-putFile($token, $key, $filepath); if ($err ! null) { throw new RuntimeException(七牛上传失败: . $err-message()); } return $this-domain . / . $key; } public function deleteObject($filepath) { $key normalizeKey($filepath); $bucketMgr new BucketManager($this-auth); list($ret, $err) $bucketMgr-delete($this-bucket, $key); // 612 表示对象不存在视为删除成功 if ($err ! null $err-code() ! 612) { throw new RuntimeException(七牛删除失败: . $err-message()); } return true; } }pushFile()里调用normalizeKey()把d/file/2024/08/01/a.jpg转成2024/08/01/a.jpg然后生成指定 key 的上传凭证。注意这里的 uploadToken 第二个参数传了$key和 2.3 节里传null的意义不同指定 key 后该凭证只能上传到这一个 key安全性更高putFile()返回的$ret数组包含hash和key两个值hash是七牛对文件内容计算的 etag可以用作后续校验但不能当 MD5 用。deleteObject()里对 612 错误码做了容错这样删除一个云端本就不存在的对象时不会因为报错而阻断 ECMS 本地的删除流程。3.3 私有空间、缩略图和正文里的图片地址怎么处理七牛空间的访问域名分公有和私有两种。公有空间直接拼domain/key就能访问私有空间则必须生成带签名的临时 URL。如果你的图片涉及商业资料或后台数据建议使用私有空间并在输出端统一处理。function privateUrl($key, $expires 3600) { $baseUrl QiniuStorage::getInstance()-domain . / . $key; $auth QiniuStorage::getInstance()-auth; return $auth-privateDownloadUrl($baseUrl, $expires); }privateDownloadUrl()会在 URL 末尾追加?e过期时间戳token签名浏览器直接访问该 URL 可正常加载图片到期后自动失效。缩略图处理则利用七牛在 URL 上追加图片处理参数的能力比如?imageView2/2/w/600就能实时生成宽度 600 的缩略图不需要本地维护多尺寸文件这对 PHP 图片处理频繁的站点能省下不少 CPU。正文里的img src/d/file/...处理上建议采用渲染层替换而不是直接改数据库。在模板或内容输出处加一个cdnReplace($html)函数把/d/file/前缀替换成七牛域名避免后台编辑器预览时图片不可见。4. 删除同步与存量附件的批量迁移上传链路改造完新图片会自动进七牛但历史积累的存量图片还留在本地删除一侧也没有联动。这一章分成两块删除文章或附件时同步清理云端对象以及把本地d/file目录里的存量文件批量迁移上去。两者都依赖 3.2 节里已经实现的deleteObject()和normalizeKey()所以这一章代码量不大重点在思路和边界情况。4.1 删除文章或附件时同步清理七牛对象ECMS 里删除图片的触发点有三个删除文章、编辑器中删除单个图片、后台附件管理中删除附件。三个入口最终都会从数据库读到附件记录拿到filepath后才能执行本地文件删除。插件要做的就是在这条删除链路里追加一行调用deleteObject($filepath)。$filepath $rs[filepath]; // 先删除七牛对象失败抛异常会阻断后续删除需要容错 try { QiniuStorage::getInstance()-deleteObject($filepath); } catch (Exception $e) { // 记录日志但不阻断本地删除 error_log([qiniu] delete failed: . $filepath . . $e-getMessage()); } // 然后执行 ECMS 原有的本地文件删除逻辑 unlink($filepath);这里把七牛删除放在本地删除之前有一个考虑如果七牛删除抛异常本地文件还在运维可以复查反过来先删本地再删云端一旦云端删除失败旧图片就永久留在了空间里只能靠控制台手动清理。unlink($filepath)前的只是示例真实代码里建议用is_file()判断后处理避免删除操作报出不必要的 PHP 警告。删除逻辑还有一个边界情况正文img src里直接指向d/file/的路径但phome_enewsfile表里没有对应的附件记录。这种情况多见于手工编辑过的内容或历史导入数据filepath拿不到就删不了云端对象。解决方法是不依赖附件表删除而是把删除动作挂在 ECMS 的文章删除接口里同时解析内容 HTML 中的所有/d/file/图片链接逐个按 key 删除这个遍历逻辑可以放在后台删除文章的公共函数中。4.2 用增量脚本把本地 d/file 全量搬到云端存量迁移是耗时最长的一步。一个中等规模的资讯站点d/file目录下可能有几万到几十万个文件直接在一个 PHP 请求里跑完不现实。推荐写一个独立的 CLI 脚本用RecursiveDirectoryIterator逐目录扫描每处理一个文件就输出一行进度挂到 nohup 后台运行。?php require __DIR__ . /vendor/autoload.php; $dir /www/wwwroot/example.com/d/file; $iterator new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if (!$file-isFile()) { continue; } $localPath $file-getPathname(); $relative substr($localPath, strlen($dir) 1); try { $url QiniuStorage::getInstance()-pushFile($dir . / . $relative); echo $relative . - . $url . PHP_EOL; } catch (Exception $e) { // 已经上传成功的跳过失败的原因可能是同名文件、权限、网络 echo FAIL . $relative . . $e-getMessage() . PHP_EOL; } }这段脚本每次运行会对d/file目录做一次全量扫描把每个本地文件传到七牛。第一次迁移时因为没有云端对象会全部上传之后再次运行时需要先判断七牛空间里是否已存在同一 key。更高效的增量做法是在pushFile()内先调用 SDK 的stat()查询对象是否存在存在则跳过。这个判断可以单独写成一个exists($key)方法上传前先检查从而避免重复消耗流量和请求配额。迁移过程还需要注意两点。第一文件名含中文或空格时七牛会返回 java 风格 URL 编码key 本身不需要额外处理但拼接访问 URL 时推荐用rawurlencode()对 key 做编码。第二图片量很大时建议分批执行一次脚本只处理某个月份或某个栏目否则单次任务跑十几个小时中途断线后没法断点续传。4.3 迁移后的校验与常见的错误判断迁移完成不等于任务结束还要验证云端对象确实存在且内容一致。七牛 SDK 的BucketManager::stat()能够查询对象信息返回结果中包含hash字段这个值就是该文件内容的 etag。校验方式比较对象说明本地 MD5 与云端 etag 对比md5_file()与$ret[hash]不成立etag 不是 MD5云端 etag 与上传返回 etag 对比$ret[hash]与$stat[hash]成立同一文件内容 etag 固定文件大小对比本地 filesize 与$stat[fsize]成立可快速筛查明显异常这里有一个容易踩的坑上传时$ret[hash]和stat()返回的$stat[hash]是同一个算法结果可以直接比较但很多人习惯拿本地md5_file()去和云端 hash 比结果永远不一致容易误判为故障。七牛 etag 的计算方式是按照文件大小分块处理后拼接的和标准 MD5 不同。如果一定要用本地哈希校验需要对大文件按 4MB 分块计算 Sha1再按七牛文档的规则拼接成本高且收益有限日常维护中比较fsize基本就够用。5. 上线前核对参数用 CLI 跑迁移并完成端到端验证这部分是交付前最容易被跳过的环节。配置里少了几个参数或者验证路径不对往往在第一个真实用户上传图片时才会炸出来。5.1 容易配错的几个参数参数推荐值说明bucketmy-ecms-img空间名称不是空间绑定的域名domainhttps://img.example.cn七牛空间绑定的访问域名末尾不要带斜杠代码里做了 rtrimzone华东华北华南等必须与空间创建区域一致跨区域请求会报 region 错误upload_max_filesize20MPHP 上传大小限制大图会被直接截断expires3600私有空间签名 URL 的有效期太短会导致图片加载失败zone参数是七牛 PHP SDK 里老版本常用的一个配置项如果使用 7.x 版本 SDK区域通常在创建空间时已经确定认证时会自动识别但如果你的 SDK 版本较旧就需要显式设置 region否则上传和删除都会报 401 或 region mismatch。5.2 上传、删除、增量迁移三项验证上线前先开一台测试服务器或者用现有站点的测试环境按三步做端到端验证。第一步上传一张小于 1MB 的测试图检查三个位置本地目录是否生成文件、phome_enewsfile表是否新增记录、七牛控制台是否出现对应对象然后打开前端页面确认图片能从七牛域名加载。第二步删除该测试文章回到七牛控制台确认对象被清理这一步同时验证了删除链路里的容错逻辑是否生效。第三步把一个只有几张图片的临时目录放入d/file执行迁移脚本观察输出结果中是否有非 200 的上传错误。5.3 用 CLI 任务跑迁移别让 PHP-FPM 阻塞站点迁移脚本的耗时不可控动辄几十分钟不适合在 PHP-FPM 请求里跑。标准做法是通过命令行执行并让任务在后台运行。nohup php /www/wwwroot/example.com/ecms/qiniu_migrate.php \ /tmp/qiniu_migrate.log 21 CLI 模式下php脚本不受max_execution_time限制但要注意脚本内部每个文件的上传请求依然会等待网络响应。如果图片数量达到几十万建议给迁移脚本加上队列化改造用 Redis 的 list 结构充当任务队列脚本只负责从队列左侧消费文件路径上传结果写入另一张日志表。这样中途断网后重启脚本只需要重新塞入未处理的 key不会把已上传的重复一遍。队列化改造还可以把迁移任务分散到多个 PHP 进程并行跑瓶颈从单进程网络请求转移到带宽和七牛 QPS 配额对大体量迁移的意义比优化上传代码本身更大。本文还有配套的精品资源点击获取
返回列表