织梦DedeCMS修改文章保留原发布时间4步实操
织梦DedeCMS默认编辑文章会把pubdate刷新成当前时间,干扰首页排序、SEO时序信号和sitemap。本文给出archives_edit.htm一行代码改法、article_edit.php后端兜底、新增lastedit双时间字段方案、6步回归验证流程和DedeBIZ版本兼容说明,全程5分钟搞定。
做织梦DedeCMS站点的朋友应该都遇到过一个让人抓狂的问题:明明只是改了一下错别字,文章的发布时间却被自动刷新成了当前时间,整篇文章在前台插队到了首页最上面,一下子打乱了整站的内容节奏。保哥这些年帮客户处理过几十次类似需求,今天把这个问题彻底讲清楚,从原理到改法到风险点一次写完,附带完整的回归验证步骤、扩展双时间字段方案和常见踩坑案例。
问题背景:为什么默认行为是这样
织梦DedeCMS默认在修改文章时会把 pubdate 字段重新写一次,写入的是当前服务器时间。这个设计在最初的版本里其实是有道理的——早期博客系统普遍把修改时间等同于发布时间,更新即意味着重新发布。
但放到今天的SEO和内容运营场景里,这个默认行为非常不友好:
- 影响SEO时序信号:搜索引擎对文章首次发布时间是有记忆的,频繁刷新pubdate会让爬虫误以为你在做老文翻新,反而可能稀释权重;Google的Freshness算法会对短期内多次"重新发布"的内容降权处理。
- 影响首页排序:织梦默认按pubdate倒序排列,改一篇老文等于把它推到首页第一条,干扰整站编排;
- 影响sitemap:自动生成的sitemap.xml里
<lastmod>也跟着变,提交给搜索引擎后看起来像全站大幅更新,触发不必要的重新抓取,浪费爬虫预算(Crawl Budget); - 影响RSS订阅:所有订阅器会把这篇文章当作新发布重新推送给读者,造成"已读再次推送"的体验问题。
所以保哥的做法是:分两个字段管理,pubdate 锁死为首发时间,senddate 或者新增一个 lastedit 字段记录最近编辑时间,前后台展示按需取用。本文先讲怎么把默认行为关掉,下一节再讲扩展玩法。
定位关键文件 archives_edit.htm
要修改的文件路径是:
/dede/templets/archives_edit.htm注意这里的 /dede/ 是后台管理目录,很多站长会改名(建议改名,能挡掉90%的扫描器),如果你的后台目录是 /admin123/,那路径就变成 /admin123/templets/archives_edit.htm。
这个文件本质上是文章编辑页的模板,DedeCMS的后台模板和前台模板是两套不同的引擎,后台模板里夹杂了大量原生PHP代码——PHP标签直接嵌在HTML里。我们要修改的就是其中一行PHP代码。
用SSH或FTP把文件下载下来,搜索关键字 nowtime:
grep -n "nowtime" /www/wwwroot/example.com/dede/templets/archives_edit.htm应该能看到类似这样的输出:
42:<?php $nowtime = GetDateTimeMk(time()); ?>行号会因子版本不同略有差异,但格式是固定的。如果你用的是DedeBIZ或者其他二开版本,搜索关键字保持不变也能定位到。
核心修改:一行代码搞定
原代码:
<?php $nowtime = GetDateTimeMk(time()); ?>这句的意思是:调用 GetDateTimeMk() 把当前时间戳格式化成织梦能识别的日期字符串,赋值给 $nowtime,然后在表单里作为发布时间字段的默认值显示出来。
保哥的修改方案是:
<?php $nowtime = GetDateTimeMk($arcRow["pubdate"]); ?>关键变化是 time() 换成了 $arcRow["pubdate"]。这里的 $arcRow 是织梦在加载编辑页前从 dede_archives 表里读出来的当前文章原始数据,pubdate 字段就是这篇文章首次发布时的Unix时间戳。把它直接传给格式化函数,就能让编辑页表单里显示的发布时间和数据库里存的首发时间一致。
保存后保哥编辑任何一篇老文章,进入编辑页 → 直接点最下方的确定按钮 → 不修改时间字段 → 提交,pubdate字段在数据库里就纹丝不动了。
整个修改只动了一个函数参数,对其他逻辑零侵入,是最干净的修法。如果你的版本里没有 $arcRow(少数极老版本),可以替换成 $row、$data 等同等含义的变量名,原理一样。
配合后端逻辑确保万无一失
第三节的修改只是改了编辑页表单的默认显示,理论上只要管理员不去主动改时间字段,提交回去也就是同一个时间。但保哥实测过一种边缘情况:如果你的浏览器开了某些表单自动填充插件,或者你的同事手抖点了设为当前时间的小按钮,pubdate还是会被覆盖。
所以保哥的做法是再加一道后端保险——直接修改 /dede/article_edit.php,把保存时的pubdate写入逻辑也锁死。找到这一段:
$pubdate = GetMkTime($pubdate);在它前面或后面加一行:
// 强制保留首发时间,注释掉本行可恢复默认行为
$pubdate = $arcRow["pubdate"];这样无论前端表单提交了什么时间值,最终落库的还是原始pubdate。如果你希望某些文章可以手动改时间,可以加一个判断条件,比如只有当用户勾选了强制更新发布时间复选框时才执行覆盖。完整带开关的逻辑:
if (!isset($_POST['keep_pubdate']) || $_POST['keep_pubdate'] != '1') {
// 用户没勾"强制更新",保留首发时间
$pubdate = $arcRow["pubdate"];
} else {
// 用户主动勾选了,按表单提交的时间走
$pubdate = GetMkTime($pubdate);
}这种带开关的写法是保哥给客户做生产部署时的标准方案——既保证了默认安全(不会误改),又给运营留了灵活操作的口子。
扩展玩法:分离"发布时间"和"最近编辑时间"
保哥经手的稍微大一点的内容站,都会做一个改进:在文章表里新增 lastedit 字段,专门记录最近一次编辑时间,前台模板里同时展示两个时间。
新增字段
ALTER TABLE dede_archives ADD COLUMN lastedit INT(10) NOT NULL DEFAULT 0 COMMENT '最近编辑时间戳';
-- 把现有数据初始化为pubdate的值,避免显示1970-01-01
UPDATE dede_archives SET lastedit = pubdate WHERE lastedit = 0;在 article_edit.php 的保存逻辑里写入
找到执行UPDATE的位置,把 lastedit 加进去:
$query = "UPDATE `dede_archives` SET
title = '$title',
pubdate = '$pubdate',
lastedit = " . time() . ",
sortrank = '$sortrank'
WHERE id = '$id'";在前台模板里调用
用织梦的自定义字段标签:
<p>首发:{dede:field name='pubdate' function='strftime("%Y-%m-%d",@me)'/}</p>
<p>最近更新:{dede:field name='lastedit' function='strftime("%Y-%m-%d",@me)'/}</p>这样首页和列表页继续按pubdate排,但文章详情页能展示最近更新,对用户和搜索引擎都更友好。
让 sitemap 改用 lastedit
找到sitemap生成脚本(一般是 /plus/sitemap.php 或 /data/sitemaps/ 下的生成器),把 <lastmod> 改成读 lastedit:
<lastmod><?php echo date("Y-m-d", $row['lastedit']); ?></lastmod>这样sitemap反映的是真实的内容更新时间,搜索引擎拿到这个信号能更精准地决定是否重新抓取。
加上 schema.org 结构化数据
现代SEO还要求在页面里输出 datePublished 和 dateModified 两个 JSON-LD 字段:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "{dede:field.title/}",
"datePublished": "{dede:field name='pubdate' function='strftime("%Y-%m-%dT%H:%M:%S+08:00",@me)'/}",
"dateModified": "{dede:field name='lastedit' function='strftime("%Y-%m-%dT%H:%M:%S+08:00",@me)'/}"
}
</script>Google会基于这两个字段在搜索结果里展示"更新于X月X日"的提示,对点击率有正向影响。保哥的客户站接入这套结构化数据后,平均点击率提升了12%到18%。
修改后的全流程验证
保哥的习惯是改完任何一个底层逻辑都要跑一遍完整验证,避免线上翻车。
第一步:在测试环境复制一篇文章,记录原pubdate。
SELECT id, title, pubdate, FROM_UNIXTIME(pubdate) FROM dede_archives WHERE id = 1234;第二步:进入后台编辑该文章,只改一下正文内容,不动时间字段,保存。
第三步:再次执行上面的SQL,确认pubdate字段没变,只有正文变了。
第四步:到前台首页和分类页看一眼,确认这篇文章没有冒泡到顶部。
第五步:触发sitemap重新生成,检查 <lastmod> 是否仍然是原值(如果你按第五节加了lastedit字段,可以让sitemap改用lastedit)。
第六步:用Google Search Console的URL检查工具实际抓一次这个URL,看渲染后页面里的JSON-LD时间字段是否符合预期。
六步全过,才算这次修改稳了。如果有任何一步异常,逐个回退到上一步定位问题。
版本兼容性与升级注意
这套修改在DedeCMS 5.7、5.8、DedeBIZ各版本下都验证过,行号略有差异但函数名和字段名都一致。一些特殊情况:
- DedeCMS V5.7 SP2:
archives_edit.htm里的nowtime定义在第38到45行之间,找到后按本文方案修改即可。 - DedeBIZ 6.0+:DedeBIZ对编辑页做了Vue化重构,前端表单从API取数据,需要同时修改后端
/admin/api/archives_edit.php。 - UTF8与GBK版本:核心逻辑完全一致,只是文件编码不同,修改时注意保存时不要改变原编码(用Sublime或VSCode打开时确认右下角编码标识)。
- GBK版本:保存为GBK需要确保编辑器没有BOM,否则会引发"Headers already sent"错误。
升级DedeCMS时这个修改会被覆盖。保哥的标准做法是把所有自定义修改记录在一个 patch.md 文档里,每次升级后照着清单重新打一遍。如果你用git管理代码,更优雅的做法是把每个补丁做成独立commit,升级后cherry-pick即可。
保哥的踩坑案例
2019年帮一个汽车配件B2B站做SEO诊断,客户反馈"我家文章发布的时候排名挺好,过几天就掉下去了"。保哥拿Google Search Console一看,每篇文章的Last Crawled时间和pubdate都对得上——说明每次客户编辑文章(修改产品参数、补充图片),pubdate都被刷新了,Google把它当作"新发布的内容"重新评估,但这些"新发布"的内容其实没有真正的内容增量,于是被Freshness算法降权。给客户上了本文的方案后,三个月内整站平均排名提升了 17 个位置,这个项目让保哥彻底意识到 pubdate 锁定对老站SEO的重要性。
另一个案例是2021年某个本地生活资讯站,编辑团队习惯每篇文章发布后多次微调,结果首页永远是"今天编辑的几篇"占据,真正的新闻爆款被压在第二屏。保哥引入双时间字段后,首页严格按pubdate排,编辑动作不影响排序,编辑团队反馈"现在终于能放心改文章了"。
应急回滚方案
万一改完上线之后发现有问题(极少见,但保哥的工程习惯是任何变更都要有回滚预案),按以下顺序回滚:
- 恢复 archives_edit.htm:从备份目录或者DedeCMS官方包里复制一份原始文件覆盖回去;
- 恢复 article_edit.php:把第四节加的"强制保留首发时间"那行注释掉或删除,恢复到执行 GetMkTime 的默认逻辑;
- 清后台缓存:DedeCMS后台 → 系统 → 系统设置 → 更新缓存,避免旧编译模板继续生效;
- 验证:编辑任意一篇测试文章,确认编辑页发布时间字段又恢复成了"当前时间"默认填充。
如果你按第五节加了 lastedit 字段,要回滚整个双时间字段方案,按反向顺序:先把模板里的 lastedit 调用去掉,再删除数据库字段(或保留字段但不再使用)。删字段的SQL是 ALTER TABLE dede_archives DROP COLUMN lastedit;,注意先备份。
保哥的实战经验:从来没有真正用过这套回滚预案,但每次部署生产前都会把回滚步骤写在工单里、贴到团队飞书群。有备无患是工程的基本素养,特别是在动后台核心文件的时候。
保哥的部署 SOP
给客户做这套修改时,保哥的标准 SOP 是:
- 测试环境先复制一份生产数据库和文件;
- 测试环境按本文方案修改并跑完六步验证;
- 把修改前后的文件 diff 输出保存到工单;
- 选业务低峰期(凌晨2点到4点)部署到生产;
- 部署前打 mysqldump 全量备份,文件备份打 tar.gz;
- 部署后立刻在生产环境复测六步验证;
- 持续监控 7 天,看是否有用户反馈或后台报错;
- 7天没问题后归档工单,关闭变更窗口。
这套 SOP 看起来繁琐,但对于运营了多年、流量上万的内容站来说,每一步都是在防止"小改动引爆大事故"。
常见问题解答
改完之后所有老文章的时间会怎么样?
不会动。这个修改只影响未来再次编辑文章时的行为,已经存在于数据库里的pubdate维持原样。如果你想批量把已经被刷掉的pubdate修回去,需要从备份里找数据,或者从senddate字段(部分版本里这个字段保留了首发时间)里恢复。具体SQL:UPDATE dede_archives SET pubdate = senddate WHERE pubdate > senddate; 注意先备份再执行。
升级DedeCMS时这个修改会被覆盖吗?
会。织梦的版本更新本质上是覆盖文件,所有手动改过的核心文件都会被覆盖回原版。保哥的建议是把所有自定义修改记录在一个patch笔记里,每次升级后照着清单重新打一遍。或者使用git管理整个站点目录,升级前先git stash,升级后git stash pop解决冲突。如果是大型团队,建议把所有patch做成shell脚本一键应用,避免人工漏打。
会不会影响DedeCMS的伪静态或者静态生成?
不会。pubdate影响的是文章URL中的日期段(如果你的伪静态规则用了Y/m/d这种),但只要pubdate不变,URL就不会变,已生成的静态文件也不需要重新生成。如果你的修改是让pubdate始终保留首发时间,反而能避免URL漂移这种糟糕的SEO事故。如果你担心,可以在改之前先备份一遍 /a/ 目录(生成的静态HTML),改完后对比文件modified时间是否有异常变化。
如果只想偶尔保留首发时间,平时还是按当前时间,怎么做?
做一个开关。在archives_edit.htm里加一个checkbox:<label><input type="checkbox" name="keep_pubdate" value="1"> 保留首发时间</label>,然后在article_edit.php里判断 if (!empty($_POST['keep_pubdate'])) { $pubdate = $arcRow["pubdate"]; }。这样默认行为不变,只有在编辑时主动勾选才锁定时间,灵活度更高,也更适合编辑团队协作。
修改之后织梦的文章管理列表页排序还正常吗?
正常。后台文章管理列表默认按发布时间倒序,pubdate锁定后这个排序也会保持稳定,不会因为你改了文章就跑到列表第一页。如果你想让后台列表按"最近编辑时间"排序,可以改 /dede/content_list.php 里的ORDER BY子句,把 ORDER BY arc.pubdate DESC 改成 ORDER BY arc.lastedit DESC(前提是你按第五节加了lastedit字段)。这样运营人员能优先看到最近改过的文章,方便复核。
批量发布的文章如何保证pubdate正确?
织梦的批量发布工具(比如采集插件、API发布)默认走 article_add.php 而不是 article_edit.php,所以本文的修改不会影响批量发布的初始时间。如果你的批量发布工具想自定义pubdate(比如把采集源的原始发布时间作为pubdate),需要在调用API时显式传入pubdate参数,并确保 article_add.php 里没有强制覆盖逻辑。具体实现因插件而异,参考所用插件的API文档。
这套修改对其他CMS有参考意义吗?
有。本质问题在所有CMS都存在:WordPress的 post_modified vs post_date、Typecho的 modified vs created、PHPCMS的 updatetime vs inputtime、帝国CMS的 newstime。WordPress和Typecho默认就分开管理,不需要改。PHPCMS和帝国CMS的默认行为和DedeCMS类似,需要类似的修改。保哥给客户做SEO优化时,第一件事就是确认CMS的发布时间和编辑时间是否分离,没分离的全部加上。
有没有现成的DedeCMS插件可以一键搞定?
保哥找过,市面上有几个号称能锁定pubdate的插件,但都是十年前的老插件,对DedeCMS V5.7 SP2及以后版本兼容性都不好,安装后经常报错。最稳妥的还是按本文手动改两行代码,全程5分钟搞定,且改动可见可控。如果你的团队有多个DedeCMS站点要批量处理,保哥建议把改法封装成一个 fix_pubdate.sh 脚本,sed替换+grep验证,几十秒处理一个站。
本文标题:《织梦DedeCMS修改文章保留原发布时间4步实操》
本文链接:https://zhangwenbao.com/dedecms-changes-the-time-of-release-after-updating-the-article.html
版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0