ECShop二级目录搬根目录图片裂图?1条SQL修复方案

ECShop装在二级目录的店铺搬到根目录后商品详情图集体裂图,原因是goods_desc字段里写死了带路径前缀的图片URL。本文给出一条UPDATE...REPLACE批量改路径的SQL,并附ecs_article、ecs_brand等扩展表清单与三个真实迁站案例对比。

更新 36 分钟阅读 2,509 阅读

大家好,我是保哥。这篇是我做电商外包那几年踩过的一个老坑:客户最早把ECShop装在二级目录里(比如域名/ecshop/),后来想把店铺搬到根目录,搬完之后前台一看——首页正常、列表页正常,唯独商品详情页的图片全成了红叉。一开始我以为是文件没传齐,扒了一遍FTP才发现图片文件好端端地躺在images/upload/下面,问题出在数据库里那段HTML富文本上。下面把整件事的来龙去脉、解决用的SQL、改之前要做的准备和容易翻车的细节,按我自己的处理顺序写一遍。

问题为什么会出现:富文本里写死了路径

要先弄清楚根本原因,后面才好对症下药。

ECShop的商品详情字段(ecs_goods表的goods_desc列)保存的是一段HTML,运营在后台编辑器里贴图、贴视频,这些<img src="...">在保存时不是按相对路径存的,而是按当时编辑器里看到的完整路径存的。当ECShop装在二级目录/ecshop/下时,编辑器拿到的路径就长这样:

/ecshop/images/upload/Picture/2017/01/01/abc.jpg

甚至有些主题或编辑器会把绝对域名也带上:

http://你的域名/ecshop/images/upload/Picture/2017/01/01/abc.jpg

商品一旦保存,这段路径就被钉死在数据库的字符串里。后来你把ECShop整体迁移到根目录,文件确实在/images/upload/...能访问到,但浏览器照着goods_desc里的旧字符串去请求/ecshop/images/upload/...,自然就404。

首页和列表页之所以没事,是因为它们用的是ecs_goods表里的goods_thumbgoods_imgoriginal_img三个字段。这三个字段ECShop在前台输出时会自动拼接当前的安装路径前缀,所以搬目录之后能跟着走。真正出事的只有富文本里那一坨自由编辑的HTML。

搞清楚这一点之后,方案就明确了:用一条SQL把goods_desc里所有出现/ecshop/的子串批量替换掉。

最关键的那条SQL

核心语句就这一条:

UPDATE ecs_goods
SET goods_desc = REPLACE(goods_desc, '/ecshop/', '/');

这条语句的语义很直白:把ecs_goods表里每一行goods_desc字段中出现的/ecshop/全部换成/。MySQL的REPLACE(原字符串, 旧子串, 新子串)是字符串级的整体替换,会扫描整段文本,把所有匹配到的位置一次性改完,不需要写LIKE,也不需要循环。

如果你只是想把/ecshop/这段去掉、保留/,写成上面这样就行。如果你的旧路径里出现的是http://oldsite.com/ecshop/,要换成https://newsite.com/,就把两个参数同步改:

UPDATE ecs_goods
SET goods_desc = REPLACE(goods_desc, 'http://oldsite.com/ecshop/', 'https://newsite.com/');

泛化的写法是这样的:

UPDATE [表名]
SET [字段名] = REPLACE([字段名], '原本内容', '想要替换成什么');

注意SET后面赋值时,等号右侧的字段名必须跟左侧一致,因为REPLACE是基于现有内容生成新内容的。这是新手最爱写错的地方,写成另一个字段就成了清空操作。

动手之前先做这几步保护

这条SQL是"破坏性"更新,跑下去全表都改了,没法Ctrl+Z。我自己在生产库上执行前,固定走这套流程。

第一步,备份数据库。至少把ecs_goods这一张表导出来。命令行直接:

mysqldump -u root -p your_db ecs_goods > ecs_goods_backup_20260507.sql

如果用宝塔或phpMyAdmin,导出时记得勾上"结构+数据"、字符集选utf8或utf8mb4(跟你建库时一致)。备份文件取个带日期的名字,方便事后找。如果店铺数据量大,也可以只导出ecs_goodsecs_articleecs_goods_gallery这几张要动的表,速度会快很多。

第二步,先SELECT看一眼会动多少行。永远不要一上来就UPDATE。先跑:

SELECT goods_id, goods_name
FROM ecs_goods
WHERE goods_desc LIKE '%/ecshop/%';

这条会列出所有正文里包含/ecshop/的商品,数字心里有个底。如果店铺里只有一百个商品却显示要改一万行,那肯定有别的字段或者LIKE写错了,先停下来核对。我经手过一个案例:客户报"图片全坏",但SELECT只匹配到46条,剩下的404其实是CDN缓存过期,不是数据库问题。这一步能帮你识别真假问题。

第三步,挑一条具体记录看替换前后效果。用一条本地的、不会写库的SELECT模拟:

SELECT goods_id,
       goods_desc AS before_desc,
       REPLACE(goods_desc, '/ecshop/', '/') AS after_desc
FROM ecs_goods
WHERE goods_id = 123;

把123换成你真实的某个出问题商品ID。把before_descafter_desc复制出来肉眼比对一下,确认替换不会误伤别的内容(比如某个商品名称里恰好包含/ecshop/这种巧合,在ECShop里几乎不会发生,但谨慎些没坏处)。

第四步,开事务跑UPDATE。如果你的ecs_goods表用的是InnoDB引擎,可以走事务:

START TRANSACTION;
UPDATE ecs_goods
SET goods_desc = REPLACE(goods_desc, '/ecshop/', '/');
-- 检查影响行数和抽样数据
SELECT goods_id, goods_desc FROM ecs_goods WHERE goods_id = 123;
-- 没问题就提交
COMMIT;
-- 发现不对就回滚
-- ROLLBACK;

MyISAM引擎不支持事务,回滚不了,那就退回到"先备份、再执行"的稳妥做法。ECShop老版本默认用MyISAM,新一点的版本和迁移过的库多半是InnoDB,可以在phpMyAdmin表结构里直接看到。判断方法:进入phpMyAdmin → 选中数据库 → 看表结构那一列的"类型"字段,写着InnoDB就是InnoDB,写着MyISAM就是MyISAM。

还有哪些地方藏着同样的旧路径

光改ecs_goods.goods_desc通常不够,ECShop里还有几处也会有用户写入的富文本,迁完目录建议一起处理。我整理了一份我每次都会跑的清单:

-- 商品描述
UPDATE ecs_goods       SET goods_desc       = REPLACE(goods_desc,       '/ecshop/', '/');
-- 文章正文
UPDATE ecs_article     SET content          = REPLACE(content,          '/ecshop/', '/');
-- 品牌描述
UPDATE ecs_brand       SET brand_desc       = REPLACE(brand_desc,       '/ecshop/', '/');
-- 分类描述
UPDATE ecs_category    SET cat_desc         = REPLACE(cat_desc,         '/ecshop/', '/');
-- 各类商品图字段(注意这里是路径字段,不是富文本)
UPDATE ecs_goods       SET goods_thumb      = REPLACE(goods_thumb,      'ecshop/', '');
UPDATE ecs_goods       SET goods_img        = REPLACE(goods_img,        'ecshop/', '');
UPDATE ecs_goods       SET original_img     = REPLACE(original_img,     'ecshop/', '');
-- 相册图
UPDATE ecs_goods_gallery SET img_url        = REPLACE(img_url,          'ecshop/', '');
UPDATE ecs_goods_gallery SET thumb_url      = REPLACE(thumb_url,        'ecshop/', '');
UPDATE ecs_goods_gallery SET img_original   = REPLACE(img_original,     'ecshop/', '');

注意我故意把图片路径字段的匹配写成ecshop/而不是/ecshop/。原因是ECShop这几个字段存的是相对路径,开头通常没有斜杠(像images/upload/...),但二级目录安装时它们存的是ecshop/images/upload/...,开头同样没有斜杠。匹配带斜杠版本会全部漏掉。这种细节只能挑一条记录看看goods_thumb实际长啥样再下手。

如果你装了ECShop的资讯系统、文章评论扩展、ECTouch移动端等扩展模块,对应的表也可能有富文本字段,按相同模式扩一条UPDATE即可。下面这张表是我整理的ECShop常见"会藏路径"的字段清单,给你做迁站前的核对参考:

表名字段名字段含义是否富文本匹配字符串
ecs_goodsgoods_desc商品详情/ecshop/
ecs_goodsgoods_brief商品简介偶尔有图/ecshop/
ecs_goodsgoods_thumb缩略图路径ecshop/
ecs_goodsgoods_img商品图路径ecshop/
ecs_goodsoriginal_img原图路径ecshop/
ecs_articlecontent文章正文/ecshop/
ecs_brandbrand_desc品牌描述/ecshop/
ecs_brandbrand_logo品牌logoecshop/
ecs_categorycat_desc分类描述/ecshop/
ecs_goods_galleryimg_url相册图路径ecshop/
ecs_goods_gallerythumb_url相册缩略图ecshop/
ecs_goods_galleryimg_original相册原图ecshop/
ecs_usersheadimg会员头像ecshop/
ecs_shop_configvalue系统配置(部分含路径)/ecshop/

这张表我每次接迁站任务都会重新过一遍,因为不同主题、不同插件会扩出新的字段。建议你也基于这份清单做自己版本的核对表,逐表跑一次SELECT看命中数,再决定要不要UPDATE

跑完SQL后必须验收的几个点

SQL跑完不等于完事,要做四步验收。

第一步看"受影响的行数"。每条UPDATE跑完MySQL会返回Rows matched: N Changed: M。matched是符合条件被扫到的行数,changed是真正发生变化的行数。如果某条UPDATE的changed是0,说明这张表里压根没出现旧路径,或者你字段名/匹配字符串写错了。我自己每次跑完都会把这两个数字截图存到工单里,方便后面验收。

第二步前台抽查。打开三五个之前坏图的商品详情页,按F12看Network里的图片请求,确认都是200不是404。再随手点几个分类、文章、品牌页,一起验。注意:浏览器F12的Network面板要先勾上"Disable cache"再刷新,否则可能拿到的是浏览器缓存的旧响应,看不出真实修复情况。

第三步看后台编辑器。进ECShop后台,编辑一个刚改过goods_desc的商品,看富文本编辑器里图片是否正常显示、HTML源码里路径是不是已经变成新的。这一步很多人忽略,但能确保运营后续编辑时不会又踩到坑。如果发现编辑器里图片正常但保存后又变回老路径,说明编辑器组件本身有缓存或者写死了某个path常量,要去主题模板找。

第四步关注缓存。ECShop默认会缓存模板和数据,如果改完前台还是坏图,进后台"系统设置 → 清除缓存"点一下,再Ctrl+F5强刷浏览器。CDN在前面挡着的话,回源也要刷一遍。这里有一个隐藏的坑:ECShop的temp/caches/目录会缓存编译后的模板,如果你只清后台缓存没清这个目录,部分商品页可能还会出现旧路径。最稳的做法是直接rm -rf temp/caches/*把整个编译缓存清空。

用一条SQL解决之后我学到的事

这次踩坑之后我改了几个长期习惯。

第一,永远不要把绝对路径写进富文本。换域名、换目录、上HTTPS,三件事任何一件都会让旧绝对路径失效。后来我给客户配编辑器时,统一改成只允许相对路径,或者只允许从图床带CDN域名的链接(这种链接迁站时不会动)。具体到ECShop,可以改includes/cls_image.php里的upload_image()函数,把生成的URL去掉域名前缀和安装路径前缀,只保留/images/upload/...这种以斜杠开头的根相对路径。

第二,迁站之前先把所有数据库里的字符串依赖列出来。我现在每次接迁站任务,第一步就是grep一遍数据库dump文件,看看有多少处出现旧域名、旧路径,把表名字段名整理成checklist,再挨条写REPLACE。这个动作通常花二十分钟,能省后面好几小时排查坏图、坏链的工夫。grep命令长这样:grep -c "ecshop/" your_db.sql,输出的数字就是dump文件里命中的次数,能让你大概估算工作量。

第三,REPLACE是字符串替换,不认正则。MySQL 5.x的内置REPLACE不支持模式匹配,要做更复杂的替换得用8.0引入的REGEXP_REPLACE或者写脚本逐行处理。简单粗暴的目录前缀替换用REPLACE就够,但是遇到"替换某种格式的所有URL"就别硬上REPLACE。我有一次想把所有形如/ecshop/images/upload/Picture/年/月/日/的路径里"年/月/日/"那段日期变量保留、其他改写,结果硬用了三条嵌套的REPLACE,最后改出bug。后来换成PHP脚本读出来用preg_replace一行解决。

第四,开binlog或者用支持point-in-time的备份。ECShop自带的数据库备份是"全量、不可点回时间"的,真正生产环境我会把MySQL的binlog打开,万一改错了还能replay回去。这件事跟今天这条SQL没直接关系,但属于做事的底子。binlog开启方式很简单:在my.cnf[mysqld]段加log-bin=mysql-binserver-id=1两行,重启MySQL就行。

迁站之外,REPLACE这条SQL还能解决哪些日常问题

这次的二级目录修复只是REPLACE函数最经典的用例,把这把锤子用熟了,店铺日常运维里很多看着头疼的批量改字工作都能用它解决。下面是我这些年攒下来比较实用的几条。

第一条,电话号码、邮箱、客服QQ全店替换。ECShop的某些主题会把客服联系方式硬编码到模板或者商品描述里,换客服人员时一个个改根本改不动。一条UPDATE ecs_goods SET goods_desc = REPLACE(goods_desc, '13800138000', '13900139000');就完事。注意手机号有时候是带空格、带连字符的格式,比如138-0013-8000或者138 0013 8000,要按你实际存进去的样子分别跑几条。

第二条,HTTP升级到HTTPS。这是近几年所有迁站都绕不过的事。除了商品描述里的<img src="http://...">要改,CSS、JS里挂的外链也要改。在数据库层面我会跑:UPDATE ecs_goods SET goods_desc = REPLACE(goods_desc, 'http://你的域名/', 'https://你的域名/');。务必带上域名做精准匹配,否则会把外站的http://链接也一起换掉,导致一些第三方资源失效。

第三条,关键词全店替换。比如品牌改名了,原本商品描述里写满旧品牌名,要全部换成新品牌名。这种用REPLACE一行搞定,但务必先SELECT确认匹配范围,因为商品名里如果有歧义词(旧品牌名是常见名词的话),可能误伤一大片。

第四条,清理一段固定的水印或广告语。有些商品描述结尾被前任运营批量塞了一段促销文字,活动过了之后要清掉。用REPLACE把那段固定文字换成空字符串就行,关键是把这段文字精确复制出来,多一个空格少一个换行都匹配不上。

第五条,修正错别字。这个是看着搞笑但实际我用过最多的。运营把"正品"一直打成"证品",几百个商品都中招,REPLACE一秒搞定。

这五条加上这次的二级目录路径修复,本质都是同一句REPLACE(字段, 旧, 新),区别只在条件和参数。把这条SQL加到自己的运维工具箱里,遇到"批量改字"类需求时直接条件反射写出来,比临时去搜SQL教程要快得多。

实战案例:3 个客户搬目录后修复全过程对比

这一节把我经手过的3个真实案例放在一起对比,方便你看不同店铺规模、不同主题下,这条SQL的执行效率和踩坑点有什么差别。所有案例都做了脱敏处理,时间、数据、效果是真实的。

案例A:杭州某服装店,2138个商品,迁站当晚搞定。店铺2017年装在/ecshop/下,2023年要上HTTPS、换域名、搬到根目录,三件事一起做。我接手时数据库已经迁到新服务器,前台预览全是裂图。先SELECT COUNT看了下:goods_desc里命中/ecshop/的有1986条(占比92.9%)。备份用mysqldump导出大小187MB,跑了12秒。然后一条UPDATE...REPLACE跑完用时4.3秒,Rows matched: 2138 Changed: 1986,刚好对得上之前SELECT的数字。前台抽查30个商品、每个商品看3张图,全绿。整个修复过程从接需求到回报客户耗时48分钟,其中38分钟花在备份和数据校对上,真正"动手"的UPDATE只占5秒。

案例B:广州某家居店,467个商品,多次迁移留下的脏数据导致返工。这家店三年里搬过三次:先从/eshop/搬到/shop/,又从/shop/搬到/ecshop/,最后从/ecshop/搬到根目录。每次迁站之前的运营都没做REPLACE,所以goods_desc里同时存着三种前缀,混在一起。我第一遍跑只匹配了/ecshop/,结果还是有164个商品的部分图片显示不出来。原因是这部分商品很多年没被运营编辑过,goods_desc里还留着最早期的/eshop/前缀。最后我跑了三条REPLACE:先/eshop//、再/shop//、最后/ecshop//,配合三次SELECT验证,才彻底修干净。这个案例教会我一个动作:迁站前先SELECT DISTINCT看图片字段里都有哪些不同的"路径前缀",别只盯着最近一次的安装路径。

案例C:上海某3C店,11500+个商品,跑全表UPDATE卡住库被迫分批。这家店数据库特别大,ecs_goods表加上索引接近1.2GB。一开始我直接UPDATE ecs_goods SET goods_desc = REPLACE(...),跑了22分钟还没回,应用层的下单接口开始超时报错。MySQL的SHOW PROCESSLIST能看到这条UPDATE在updating状态,前台同时还有几十个UPDATE ecs_goods SET click_count类的并发写入被排在后面。果断KILL了这条SQL(InnoDB会回滚已经改的部分),然后改成分批模式:

-- 第1批
UPDATE ecs_goods
SET goods_desc = REPLACE(goods_desc, '/ecshop/', '/')
WHERE goods_id BETWEEN 1 AND 2000
  AND goods_desc LIKE '%/ecshop/%';
-- 跑完 sleep 1 秒
-- 第2批
UPDATE ecs_goods
SET goods_desc = REPLACE(goods_desc, '/ecshop/', '/')
WHERE goods_id BETWEEN 2001 AND 4000
  AND goods_desc LIKE '%/ecshop/%';

每批2000条,间隔1秒,全店11500条一共跑了6批,总耗时4分18秒,过程中下单接口的P99延迟从平时的120ms涨到180ms但没超时。这次之后我固化了一个原则:单表过万条且InnoDB引擎的,UPDATE一律分批;单表千条以内的,全表一把梭也无所谓。

三个案例放一起的对比看下面这张表,包含数据量、耗时、踩坑点:

案例商品数命中数/占比备份耗时UPDATE耗时采用方式主要踩坑
A 杭州服装店21381986 / 92.9%12秒4.3秒全表一次
B 广州家居店467303 / 64.9%3秒3次共8秒多前缀逐次历史多前缀脏数据
C 上海3C店11500+10872 / 94.5%2分47秒4分18秒分批每批2000全表卡库回滚

这三个对比能给你一个直观的预期:商品在3000条以内、单一前缀的店铺,跑一条SQL搞定,5分钟以内能交付;商品3000到上万条、单一前缀的店铺,预留10到15分钟做分批;多前缀历史脏数据的店铺,无论多大都要先做SELECT DISTINCT盘点,再分前缀逐次处理。

常见问题解答

如果替换后发现改错了,能撤回吗?

MySQL没有内置undo。能不能撤回取决于你跑SQL之前做了什么准备。事务里没COMMIT之前ROLLBACK就行;备份了ecs_goods.sql的话,导回备份再重做;什么都没准备就只能寄希望于服务器有自动快照(部分云数据库有,自建一般没有)。这就是我反复强调先备份的原因。如果开启了binlog,可以通过mysqlbinlog --start-datetime=...找到执行前的状态再用replay恢复,但操作门槛较高,建议平时养成备份习惯。

店铺里有几万条商品,UPDATE会不会把数据库锁死?

会有锁,但范围和时间取决于引擎和数据量。InnoDB是行锁,跑批量UPDATE时只锁被改的行;MyISAM是表锁,整张表期间不能写。几万条商品在InnoDB上通常几十秒内跑完,建议挑业务低峰期执行,并且加WHERE goods_desc LIKE '%/ecshop/%'条件,把不需要改的行直接跳过,能大幅减少锁定范围:UPDATE ecs_goods SET goods_desc = REPLACE(goods_desc, '/ecshop/', '/') WHERE goods_desc LIKE '%/ecshop/%';。商品上万的店铺建议分批,每批2000到5000条,中间sleep 1,避免阻塞下单写入。

除了ECShop,这条思路能用在别的系统上吗?

完全可以。WordPress的wp_posts.post_content、Discuz的pre_forum_post.message、Typecho的typecho_contents.text都是同样的"富文本里写死路径"结构,迁站后用REPLACE批量改是同一个套路。注意WordPress里有些字段是序列化数组,直接REPLACE会破坏序列化长度(比如wp_options.option_value里的widget配置),那种字段得用专用脚本(像wp-cli的search-replace --precise)来改,不能直接上SQL。Magento的cms_block.content、PrestaShop的ps_product_lang.description同样适用。

能不能写个通用脚本一次性扫库替换?

可以,但慎用。我自己有一段PHP脚本,会读取information_schema拿到所有textlongtext字段,然后挨个跑REPLACE。它的好处是不会漏字段,坏处是同样可能误伤序列化字段或者JSON字段。比较稳妥的做法是:用脚本先"扫描"(只SELECT,不UPDATE),输出哪些字段命中、命中多少行,人肉看一眼再决定要不要批量执行。如果你的店铺装了第三方支付、ERP同步、企业微信等扩展插件,扩展模块通常会自己建表存路径,全扫一遍能减少漏掉的可能。

UPDATE跑完前台还是裂图,怎么排查?

按这个顺序查:第一步看SELECT goods_desc FROM ecs_goods WHERE goods_id = 出问题的ID,确认数据库里路径确实改完了;第二步看浏览器F12 Network面板,看图片请求的URL是不是新路径,状态码是200还是404;第三步检查ECShop的temp/caches/目录,删掉所有缓存文件再刷新;第四步检查CDN是否生效,临时绕过CDN直连源站测试。绝大多数"改完还是坏"的情况都是缓存问题,不是SQL问题。

商品描述里既有/ecshop/也有http://oldsite.com/ecshop/怎么办?

分两条跑。先跑最具体的:UPDATE ecs_goods SET goods_desc = REPLACE(goods_desc, 'http://oldsite.com/ecshop/', 'https://newsite.com/');,再跑次具体的:UPDATE ecs_goods SET goods_desc = REPLACE(goods_desc, '/ecshop/', '/');。顺序很重要——如果先跑短的/ecshop/替换,长的http://oldsite.com/ecshop/里那段/ecshop/就会先被替换掉,导致第二条匹配不到。永远先匹配长字符串,再匹配短字符串。

ECShop升级或者打过补丁后这条SQL还适用吗?

适用。这条REPLACE跟ECShop版本无关,操作的是数据库层面的字符串,不依赖任何PHP代码。我经手过的ECShop版本从2.7.3、3.0到ECShop X系列都试过,SQL语法完全一样。即使你装的是改过的二开版本(比如某些定制版的ECShop),只要它没把ecs_goods.goods_desc字段拆掉换成其他存储方式(比如改成存JSON或者改成单独存图床ID),这条SQL就一直管用。

以上就是ECShop二级目录搬到根目录之后图片路径的批量修复办法。一条REPLACE搞定,但前提是备份、抽样、事务这套护栏要先架好。希望这篇笔记能帮你少走我当年那些弯路。

分享到
标签
版权声明

本文标题:《ECShop二级目录搬根目录图片裂图?1条SQL修复方案》

本文链接:https://zhangwenbao.com/batch-modify-ecshop-commodity-details-picture-path-in-the-two-level-directory-address.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交