本文约 4,014 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:数据库里有记录,文件也在,但图片就是打不开
迁移后最常见的图片问题:媒体库里能看到图片缩略图(或看到裂图),前台所有图片 404。
这种情况通常是物理文件位置与数据库记录不一致——数据库说图片在 2026/10/xxx.jpg,实际文件却在 uploads/xxx.jpg 或 uploads/sites/2/2026/10/xxx.jpg。
原理分析:WordPress 的文件组织规则
目录结构
wp-content/uploads/
├── 2026/
│ ├── 01/
│ ├── 02/
│ └── 10/
│ ├── photo.jpg
│ ├── photo-150x150.jpg 缩略图
│ └── photo-300x200.jpg
└── sites/2/2026/10/ 多站点子站
是否按年月分目录由后台”设置 → 媒体 → 将上传文件按年月目录组织”控制,也可以用常量:
define('UPLOADS', 'wp-content/uploads'); // 相对 ABSPATH
数据库记录
每个附件在 wp_posts 里是一条 post_type=attachment 的记录,其物理路径存在 wp_postmeta:
_wp_attached_file = "2026/10/photo.jpg" ← 相对 uploads 的路径
_wp_attachment_metadata = 序列化数组(含各尺寸文件名与宽高)
guid = "https://site.com/wp-content/uploads/2026/10/photo.jpg"
所以完整的图片 URL = uploads 的 baseurl + _wp_attached_file 的值。
两边对不上就会 404。
保姆级实操
第一步:确认当前配置
wp option get upload_path
wp option get upload_url_path
wp eval 'print_r(wp_upload_dir());'
输出里的 basedir(物理路径)与 baseurl(访问 URL)是判断依据。
第二步:核对数据库记录与实际文件
SELECT p.ID, p.post_title, m.meta_value
FROM wp_posts p
JOIN wp_postmeta m ON p.ID = m.post_id
WHERE p.post_type='attachment'
AND m.meta_key='_wp_attached_file'
LIMIT 20;
然后到服务器上看文件是否真的在那个位置:
ls -la /var/www/site/wp-content/uploads/2026/10/ | head
第三步:批量核对脚本
#!/usr/bin/env bash
# check-uploads.sh <站点目录>
SITE="${1:?用法: $0 <站点目录>}"
UPLOADS="$SITE/wp-content/uploads"
mysql -N -e "SELECT meta_value FROM wp_postmeta WHERE meta_key='_wp_attached_file';" wordpress | while read -r f; do
if [ ! -f "$UPLOADS/$f" ]; then
echo "缺失: $f"
fi
done | head -50
第四步:修复路径不一致(三种情况)
情况 A:文件都在,只是目录层级错了
比如文件实际在 uploads/2026/10/ 但数据库记录是 sites/2/2026/10/:
wp search-replace 'sites/2/' '' --include-columns=_wp_attached_file --dry-run
wp search-replace 'sites/2/' '' wp_postmeta --precise
注意限定表,避免误伤其它数据。
情况 B:文件被平铺在 uploads 根目录
需要从 guid 或文件名推断原路径,批量移动:
cd /var/www/site/wp-content/uploads
# 按文件修改时间归档(示例)
for f in *.jpg; do
Y=$(date -r "$f" +%Y)
M=$(date -r "$f" +%m)
mkdir -p "$Y/$M"
mv "$f" "$Y/$M/"
done
然后更新数据库记录:
-- 谨慎执行,先 SELECT 验证
UPDATE wp_postmeta pm
JOIN wp_posts p ON p.ID = pm.post_id
SET pm.meta_value = CONCAT('2026/10/', SUBSTRING_INDEX(pm.meta_value, '/', -1))
WHERE pm.meta_key = '_wp_attached_file'
AND pm.meta_value NOT LIKE '%/%';
情况 C:文件根本没拷过来
从备份或旧服务器同步:
rsync -avP root@old-server:/var/www/site/wp-content/uploads/ \
/var/www/site/wp-content/uploads/
chown -R www-data:www-data /var/www/site/wp-content/uploads
第五步:重建缩略图
文件到位但尺寸文件缺失(-150x150.jpg 之类):
wp media regenerate --yes
只重建缺失的:
wp media regenerate --only-missing --yes
第六步:修正权限与属主
find /var/www/site/wp-content/uploads -type d -exec chmod 755 {} \;
find /var/www/site/wp-content/uploads -type f -exec chmod 644 {} \;
chown -R www-data:www-data /var/www/site/wp-content/uploads
第七步:修正 URL(域名变了的情况)
wp search-replace 'https://old.com/wp-content/uploads' \
'https://new.com/wp-content/uploads' \
--all-tables --precise --skip-columns=guid
第八步:验证
# 抽查几张图
wp eval '
$q = new WP_Query(["post_type"=>"attachment","posts_per_page"=>5]);
foreach($q->posts as $p){ echo wp_get_attachment_url($p->ID), PHP_EOL; }
'
curl -I "$(上一步输出的某个 URL)"
应返回 200。
常见踩坑与失败案例
- 关闭了”按年月组织”后旧图片 404 —— 这个设置只影响新上传,已有文件的路径不会变。关闭后旧记录仍然指向
2026/10/,不要手工改旧记录。 - 案例:某站迁移时只拷了
uploads/2026/而漏了uploads/2025/,结果 2025 年的图片全部 404。用上面的核对脚本能一次性列出所有缺失文件。 _wp_attachment_metadata与_wp_attached_file不一致 —— 前者是序列化数组,直接 SQL 替换会破坏。用 WP-CLI 的search-replace(会重算序列化)而不是 SQL。- 多站点路径含 sites/2 —— 拆分为单站后必须去掉
sites/2/前缀,否则全部裂图。 - Windows 迁移到 Linux 路径分隔符 —— 记录里可能有反斜杠,需要替换。
- 对象存储插件路径 —— 用了 OSS/COS 插件后,实际文件在云端,本地可能没有文件,这时”缺失”是正常的,不要盲目修复。
总结
媒体库路径问题的排查顺序是:确认 uploads 的 basedir/baseurl → 核对数据库记录与实际文件 → 判断是路径不一致还是文件缺失 → 同步文件 / 修正记录 → 重建缩略图 → 修正权限 → curl 验证。核心记忆点是那个公式:图片 URL = uploads baseurl + _wp_attached_file,两边任何一处对不上都会 404。改记录时务必用 WP-CLI 的 search-replace(保护序列化数据),别直接用 SQL REPLACE。
