保哥笔记

织梦置顶一天怎么加?sortrank改造完整方案

2014 年到 2018 年我用织梦做过 6 个新闻类站点,最常被编辑投诉的就是置顶机制。织梦自带的 sortup 下拉只给"置顶一周/一月/三个月/半年/一年"五档,最短的"一周"对快讯节奏来说仍然太长——编辑想让一条快讯在首页顶上一天就够,第二天自动落回时间序,但织梦默认做不到。这篇笔记把我当年改造 sortup 字段的全过程整理成一份可直接复用的方案,包括模板修改、字段含义、批量编辑、自定义内容模型兼容、置顶到期自动化处理,以及一个被很多教程忽略的细节——批量操作 ajax 白名单。

所有代码我都在织梦 5.7sp1(最后一个官方版本)和 dedebiz 0.8.x 分支上验证过。dedeCMS 官方早在 2022 年宣布停更并由社区分叉,但生产环境上仍有大量站点在跑 5.7sp1,本文方案对这两个分支都适用。

织梦置顶机制的底层逻辑

要改置顶时长,先得搞清楚织梦怎么实现置顶。打开 dede_archives 表,会看到一个 sortrank 字段——置顶逻辑就藏在它身上。织梦把 sortrank 默认设为发布时间的 Unix 时间戳,列表页 SQL 是按 ORDER BY sortrank DESC 排序的,所以越新的文章 sortrank 越大、排在越前面。

当编辑选择"置顶一周"时,织梦实际做的事是:把 sortrank 改成"当前时间戳 + 7 × 86400",相当于这条文章的"虚拟发布时间"被推到一周后。一周内任何新发布的文章都没办法用真实时间戳超过它,自然就被顶在首页。等真实时间走过这个未来时间点之后,新文章重新追上,被置顶的内容自动落回时间序。

这个机制聪明的地方在于:

理解了这一点,加"置顶一天"就只是在选项列表里多塞一行 value="1",没有任何数据库结构改动,回滚极其简单。这种"用最小改动得到最大复用"的设计哲学是织梦早期开发组的特色,可惜后期产品停滞才让人忽略了它的工程美感。

定位文件:article_add 与 article_edit 的下拉源

织梦后台所有发布、编辑表单都在 dede/templets 目录下。文章对应的两个核心模板是 article_add.htm(新建)和 article_edit.htm(编辑)。两份模板里都有一段 sortup 下拉框:

<td width="250">
  <select name="sortup" id="sortup">
    <option value="0">正常排序</option>
    <option value="7">置顶一周</option>
    <option value="30">置顶一个月</option>
    <option value="90">置顶三个月</option>
    <option value="180">置顶半年</option>
    <option value="360">置顶一年</option>
  </select>
</td>

这里的 value 值就是"置顶天数",提交表单后织梦会乘以 86400 加到当前时间戳上,写回 sortrank 字段。我们要做的就是在这段下拉里增加 value="1" 的选项。

放在哪个位置最合理

建议把新选项放在"正常排序"和"置顶一周"之间,从短到长排列符合用户阅读习惯,编辑选起来更顺手:

<select name="sortup" id="sortup">
  <option value="0">正常排序</option>
  <option value="1">置顶一天</option>
  <option value="3">置顶三天</option>
  <option value="7">置顶一周</option>
  <option value="30">置顶一个月</option>
  <option value="90">置顶三个月</option>
  <option value="180">置顶半年</option>
  <option value="360">置顶一年</option>
</select>

顺手把"置顶三天"也加上——快讯类内容三天比一周更合适,是被遗漏的中间档。两份模板都要改,新建页和编辑页要保持一致,否则会出现"新建时能选一天、编辑时却没有这个选项"的诡异情况,编辑改完原来一天的文章会变成默认排序。

改造步骤:备份、修改、回写、验证

改造流程一共四步,每步都不能跳。

备份

SSH 登录服务器把 dede/templets 整个目录打包一份,文件命名带日期,万一改坏了至少能秒回滚:

cd /www/wwwroot/yoursite
tar -czf templets-backup-20260507.tar.gz dede/templets/

顺便把当前文件 hash 值记录一下,回滚时可以快速比对:

md5sum dede/templets/article_add.htm dede/templets/article_edit.htm > templets-md5-20260507.txt

修改

用 vim 或者通过宝塔面板编辑器打开 dede/templets/article_add.htm,搜索 sortup 定位到下拉框,按上一节给出的内容插入新行。然后打开 dede/templets/article_edit.htm 做同样修改。

织梦 5.7sp1 里这两段代码大致在 article_edit.htm 第 423 行附近、article_add.htm 第 502 行附近,但具体行号会随主题模板和打补丁顺序略有偏移,搜索关键词比死记行号靠谱

回写

如果是用本地 IDE 改的,必须用二进制方式上传 htm 文件,避免编辑器把换行符从 LF 改成 CRLF 后织梦解析异常。Linux 服务器上推荐 LF,Windows 自带记事本会偷偷把它改成 CRLF——这是常年被坑的雷。VSCode/PHPStorm 都能在状态栏直接切换 LF/CRLF。

FTP 上传时主动选 binary 模式,自动模式下 .htm 后缀容易被识别为文本而触发自动换行符转换。

验证

登录织梦后台进入"核心 → 内容发布",新建一篇测试文章,检查 sortup 下拉里是否多出了"置顶一天"选项,选中并保存。然后去文章管理列表里编辑这篇文章,再次确认下拉里也有"置顶一天",并且当前选中状态正确。

用 SQL 直接查 sortrank 字段确认置顶逻辑生效:

SELECT id, title, sortrank, FROM_UNIXTIME(sortrank) AS sort_time
FROM dede_archives
WHERE id = 你的测试文章ID;

查询结果里 sort_time 应该比当前时间晚一天左右,这说明置顶一天已经写入。如果显示的是当前时间附近,那是表单提交逻辑没拿到新 value,需要回查 article_add.php 的处理代码。

二次开发兼容:自定义模型与批量编辑

自定义内容模型

如果你的站点有自定义内容模型——比如 spec_add.htm/spec_edit.htm 对应专题,或者 archives_add.htm 对应自由文档——每一个独立内容模型都有自己的 sortup 下拉,需要单独修改。织梦的模板复用程度不高,一处改不能全站生效,这是它的老毛病。

建议写一个简单的 shell 命令扫一遍 dede/templets 目录里所有包含 sortup 关键字的文件:

grep -l "sortup" dede/templets/*.htm

输出的文件列表就是需要修改的全部位置。改完之后再 grep 一次,确认所有文件都已经包含 value="1" 这一行:

grep -c 'value="1"' dede/templets/*.htm | grep -v ':0$'

这条命令会列出含 value="1" 的文件以及出现次数,方便核查覆盖完整度。

批量编辑的 ajax 白名单

这个细节很多教程都漏掉了——织梦后台列表页的"更多操作"按钮里有批量置顶功能,但它的天数白名单是硬编码的。要让批量操作也支持"置顶一天",需要修改 dede/content_list.php 或对应的 ajax 处理文件 dede/ajax_album.php/dede/content_list_ajax.php(不同补丁版本文件名不同)。

定位办法:在 dede 目录下 grep "sortup":

grep -rn "sortup" dede/ | grep -v templets

找到对应的 PHP 文件,把允许的天数白名单从:

$allowedDays = array(7, 30, 90, 180, 360);

扩展为:

$allowedDays = array(1, 3, 7, 30, 90, 180, 360);

这步对一般站点不是必须,因为绝大多数编辑还是单篇操作。如果你的运营团队真的有批量短置顶的需求(比如发完一组热点快讯统一置顶一天),再考虑加上。

自动化与运营层延伸

改完模板后通常还要做几件事让这个功能在运营层更顺手。

视觉提示:今日置顶徽章

在文章列表模板里判断 sortrank 是否在"未来一天内",是的话在标题前加一个红色徽章,让运营和编辑一眼看清哪些是短期置顶:

<?php if ($fields['sortrank'] > time() && $fields['sortrank'] - time() <= 86400) : ?>
  <span class="badge-today-top">今日置顶</span>
<?php endif; ?>

样式:

.badge-today-top {
  display: inline-block;
  padding: 2px 6px;
  background: #f56c6c;
  color: #fff;
  font-size: 11px;
  border-radius: 3px;
  margin-right: 6px;
}

定时巡检:超量置顶报警

虽然织梦置顶能自动到期回落,但编辑有时候会手滑把多条快讯都置顶,首页瞬间被顶满,反而稀释流量分配。写一个 cron 任务每小时跑一次,把超过五条同时置顶的情况发到企业微信或钉钉报警,让值班编辑及时清理:

<?php
// /www/wwwroot/yoursite/scripts/check_top_count.php
require_once dirname(__FILE__) . '/../include/common.inc.php';
$now = time();
$row = $dsql->getOne("SELECT COUNT(*) AS c FROM dede_archives WHERE sortrank > {$now}");
if ($row['c'] > 5) {
    $msg = "首页置顶数已达 {$row['c']} 条,请检查是否有误顶";
    // 推送到企业微信 webhook
    file_get_contents("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY&msg=" . urlencode($msg));
}

crontab 配置:

0 * * * * /usr/bin/php /www/wwwroot/yoursite/scripts/check_top_count.php

埋统计:跟踪置顶位的引流效果

给短期置顶的文章 URL 加一个 utm 参数,专门跟踪从首页置顶位过来的点击量。在首页模板里判断置顶状态,加 utm:

<?php
$utm = '';
if ($fields['sortrank'] > time()) {
    $utm = '?utm_source=site&utm_medium=hometop&utm_campaign=' .
           date('Y-m-d', $fields['sortrank']);
}
?>
<a href="<?php echo $url . $utm; ?>"><?php echo $title; ?></a>

跑一周数据就能知道"置顶一天"对快讯是否真的有引流效果。我自己运营过的一个站点跑出来的数据:置顶位文章 24 小时内引流是非置顶位的 3.4 倍,但 7 天累计反而是非置顶位高 1.8 倍——说明短期置顶在快讯上有效,长期置顶反而压制其他内容总量,这个反直觉的结论是改造完才看到的。

性能与缓存:改完之后必须刷的几处

织梦的列表页一般都是生成静态 HTML 的,改完模板和置顶后,必须刷新对应静态页才能让前台用户看到新顺序:

  1. 首页静态:后台"生成 → 更新主页 HTML"
  2. 栏目页静态:后台"生成 → 更新栏目 HTML",要勾选"重新生成所有栏目"
  3. 列表页静态:栏目页里点"更新列表"
  4. CDN 缓存:如果挂了 Cloudflare/七牛等 CDN,触发一次 purge cache
  5. 内存缓存:用了 Memcached 或 Redis 缓存的话,调用 data/cache 目录清理脚本

大型站点的全站重新生成可能耗时几分钟到几十分钟,建议在低峰时段操作。如果只想刷新当天置顶的那一篇,单篇内容页生成即可,不用全站重生成。

跟其他 CMS 的实现对比

顺便对比一下其他 CMS 的置顶机制,理解不同设计取舍:

从工程角度看,织梦的"sortrank 时间戳算术"是最优解:零额外字段、零定时任务、自动到期。现代 CMS 应该多借鉴这种思路,而不是堆栈各种插件来弥补核心功能。

跟 sortrank 排序冲突的常见情况

实战中有几类容易混淆的"看起来没生效"情况:

情况一:列表页 SQL 没按 sortrank 排序

有些自定义模板(特别是从其他来源复制过来的)列表标签写成了 orderby='id desc' 而不是 orderby='sort',这种情况下 sortrank 改了也没用。织梦默认的列表标签是 orderby='sort',对应的就是 sortrank。检查方法:找到当前列表模板(一般是 templets/default/list_article.htm 等),grep "orderby",确认是 sort 而非 id/pubdate

情况二:用了置顶但列表里仍按时间倒序

这是 SQL 没用 sortrank 但用了 pubdate 的典型表现。织梦在 5.7sp1 之前有个补丁修复过这个问题,如果你的站点版本特别老(5.7 未打 sp1)建议先升级。

情况三:批量置顶后只生效部分文章

批量操作 ajax 接口的天数白名单没改全,导致 value="1" 提交过去被服务端拒绝(默默改回 0)。检查方法见上一节"批量编辑的 ajax 白名单"。

情况四:置顶时间精度问题

sortrank 是秒级时间戳,但织梦后台批量置顶时是按"天"算的——也就是从当前时间起整 N 天后。这意味着如果你下午 3 点置顶 1 天,第二天下午 3 点才到期,不是当天 24 点到期。需要"当日置顶"语义的话,要单独写逻辑把 sortrank 设为"今晚 23:59:59 的时间戳"而不是"现在 + 86400"。

什么时候应该放弃改织梦换 CMS

说点逆耳的话。织梦虽然好用、机制聪明,但官方 2022 年停更后已经没有安全更新。如果你的站点有以下任一情况,建议直接换其他 CMS 而不是继续二开织梦:

  1. 有金融/医疗/政务类敏感数据:织梦历史漏洞多(栏目跨站、SQL 注入、任意文件上传),停更后新发现的漏洞没人修
  2. 需要 HTTPS 严格合规:织梦默认输出大量 HTTP 资源,全站迁 HTTPS 工作量极大
  3. 有移动端/小程序对接:织梦没原生 API,每接一处都要写适配
  4. 需要长期维护超过 3 年:长期来看托管成本超过迁移成本

替代方案:内容站换 Typecho 或 WordPress;电商类换 Shopify 或 WooCommerce;门户类换 Strapi 或 Directus(headless)。迁移成本一次性高,但长期回报远大于继续给织梦打补丁。

常见问题解答

改完模板后下拉里没有"置顶一天",是不是织梦缓存问题?

有可能。织梦后台的模板修改一般是即时生效的,但如果你启用了 Opcache 或 PHP 加速器,旧模板可能还被缓存。重启 PHP-FPM 或在面板里点一次清空 Opcache,刷新后台页面再看。如果还没出现,回头检查是不是改错了文件,比如把 article_add.htm 改成了 article_add.bak.htm 这种带后缀的备份。还有一种可能是浏览器缓存了 htm 表单,强制刷新(Ctrl+Shift+R)即可。

选了"置顶一天"但第二天没自动取消,怎么办?

先用 SQL 查这条文章的 sortrank 字段,把数值用 FROM_UNIXTIME 换成可读时间,看是不是真的写成了未来一天。如果时间正确但首页依然置顶,多半是首页静态缓存还没刷新,去后台"生成 → 更新主页 HTML"跑一遍即可。如果连 sortrank 都不对,那是表单提交逻辑问题,检查 dede/article_add.php 里有没有改过 sortup 的处理代码。

这个改动会影响以前已经置顶的文章吗?

不会。我们只增加了下拉选项,没有动数据库里任何已有数据,已经置顶一周、一个月的文章 sortrank 字段保持不变,到期照样自动回落。回滚也很简单,把模板里那行 option 删掉、或者直接还原备份目录就行。整个改造是无状态的,对生产数据零影响。

5.7sp1 之后的版本(dedebiz 分支等)这个方法还能用吗?

实测在 dedebiz 上同样适用。dedebiz 是社区维护的分支,sortrank 机制没有改动,只是部分模板路径有调整,搜索 sortup 关键字定位即可。如果你用的是更激进的二次开发版本,比如有人把 sortup 改成了独立的 expire 字段,那需要单独看那个版本的实现,本文方案不能直接套。dedeV6 等社区分支还在用 sortrank,所以方案通用。

批量置顶时新选项不生效,是不是只改前端就行?

不行。批量操作走 ajax,服务端有天数白名单硬编码。要让批量操作也支持新天数,必须同时改 dede/content_list.php 或对应的 ajax 处理文件里的 allowedDays 数组。grep -rn sortup dede/ 找到对应 PHP 文件,把白名单从 array(7,30,90,180,360) 扩展为 array(1,3,7,30,90,180,360)。这一步漏掉是教程里最常见的坑。

置顶一天能不能改成"今晚到期"而不是"24 小时后到期"?

可以但要单独写逻辑。织梦默认 sortup 提交的是天数乘以 86400 加当前时间戳,所以下午置顶就是第二天下午到期。要做"今晚到期"语义,需要在 article_add.php 处理 sortup 时判断 value 是不是特殊标记(比如 endofday),如果是就把 sortrank 设为 mktime(23,59,59) 而不是 time()+86400。这种改动需要懂织梦表单处理逻辑,属于二次开发不是简单模板改。

改完之后想让"置顶一天"在前端有特殊样式怎么办?

在文章列表模板里加判断:sortrank 大于当前时间戳且差值小于等于 86400 时,渲染一个红色徽章。HTML 用 span class,CSS 给个 background 颜色和 padding。判断条件用 PHP 写:if (\$fields['sortrank'] > time() && \$fields['sortrank'] - time() <= 86400)。这样即使编辑选了置顶一天但忘了刷新静态页,前端用户也能看到醒目的标识。

织梦官方停更了,还要不要继续在它上面做二次开发?

分情况。如果是稳定运行多年、没有敏感数据、流量稳定的站点,继续维护是划算的,毕竟迁移成本巨大;但如果是新建站点,2026 年了不建议再选织梦——官方停更后安全漏洞没人修,每年新增 CVE 都要自己打补丁。新站建议 WordPress(生态最完善)或 Typecho(轻量优雅),电商类直接 WooCommerce 或 Shopify,门户类考虑 headless CMS(Strapi/Directus)。