Discuz X3.5 标题省略号去除:2 种修复方法
保哥昨天把一个地方论坛升级到最新的 Discuz X3.5,没想到落了一个挺烦人的坑——门户发布文章时只要标题字数超过限制,系统就自动在标题末尾追加一段省略号"…",而且每编辑保存一次就追加一次,几次之后标题尾巴变成"……………",活活把文章列表搞得像得了帕金森症的电报机。
这种情况在 Discuz X3.4 时代是直接改代码里写死的 80 解决,X3.5 升级后底层把这个数字抽出来变成了 $_G['setting']['maxsubjectsize'] 全局变量,原来熟悉的修改路径找不到了。本文记录保哥从踩坑到搞清楚 X3.5 整套"标题最大字数"机制的全过程,附两种修复方案、升级踩坑提醒、相关配置项的连带影响、以及完整 FAQ。
一、问题表现与影响范围
升级到 Discuz X3.5 之后,门户发布文章流程里会出现这些症状:
- 标题字数超过 80 字(默认值)会被截断,末尾追加
…省略号。 - 编辑同一篇文章重新保存时,被截断的标题再次进入截断逻辑,再加一次省略号——多次编辑后省略号会越堆越多。
- 影响范围不仅是门户文章,论坛主题、用户日志、群组主题、问答等所有走
getstr()函数处理标题的地方都会受影响。 - 最直观的表现是首页门户区块、文章列表的标题尾巴出现
…………这种叠加省略号,看起来非常不专业。 - 对 SEO 也不友好:搜索引擎抓取的
<title>内容是带省略号的截断版,关键词被截掉,相关性下降。
如果是新装站点这个问题不容易发现,因为多数测试文章标题不会超过 80 字。但升级到 X3.5 的老站很容易踩坑——尤其是从 X3.2/X3.4 一路升级、原本设置过更大标题长度的论坛,会发现升级后所有长标题集体"被截断"。
二、根因:maxsubjectsize 配置项的演变
翻看 Discuz 源码可以发现,标题截断逻辑在 source/function/function_core.php 里的 getstr() 函数中实现。函数原型大致是:
function getstr($string, $length = 0, $in_slashes = 0, $out_slashes = 0, $censor = 0, $html = 0) {
// ...截断与省略号追加...
}
调用点在 source/include/portalcp/portalcp_article.php:
$_POST['title'] = getstr(trim($_POST['title']), $_G['setting']['maxsubjectsize']);
$_POST['pagetitle'] = getstr(trim($_POST['pagetitle']), $_G['setting']['maxsubjectsize']);
X3.4 时代这个 maxsubjectsize 是写死的 80,所以解决方法就是直接把 80 改成你想要的值。X3.5 把它抽成了全局变量 $_G['setting']['maxsubjectsize'],理论上可以从后台配置——但这个配置项的入口在初版 X3.5 里被开发人员漏掉了 UI,导致一段时间内只能改源码。
幸运的是 X3.5 后续小版本里把这个 UI 补上去了。下面的方案 A 是推荐做法(用后台 UI 改),方案 B 是兜底(直接改源码),保哥都讲清楚。
三、方案 A(推荐):后台全局配置
登录 Discuz 后台 → 全局 → 用户权限 → 标题最大字数,把默认值 80 改成 255(或者你需要的任意小于 255 的数值),保存。
这一项有几个细节需要留意:
- 影响范围:论坛、门户、日志均受此值限制。
- 管理组豁免:管理组成员可以通过用户组里的"发帖不受限制"权限在论坛模块绕过限制(但门户和日志依然受限)。
- 上限是 255:因为底层数据库
portal_article_title、forum_thread.subject等字段类型是varchar(255),超过 255 就要改表结构了。 - 下限要避免设为 0:设为 0 不是"不限",而是字段会被截成空字符串。
- 配置生效需要清缓存:保存后建议在后台"工具 → 更新缓存"里勾选"全部更新"再点提交,否则部分模块还在用老值。
这套方案适合 99% 的场景,简单、安全、可逆,不需要改代码。如果你的 Discuz 版本后台找不到这个选项,再走方案 B。
四、方案 B:直接改源码
找到 source/include/portalcp/portalcp_article.php,定位到这两行:
$_POST['title'] = getstr(trim($_POST['title']), $_G['setting']['maxsubjectsize']);
$_POST['pagetitle'] = getstr(trim($_POST['pagetitle']), $_G['setting']['maxsubjectsize']);
把两处 $_G['setting']['maxsubjectsize'] 都改成具体数字,比如 255:
$_POST['title'] = getstr(trim($_POST['title']), 255);
$_POST['pagetitle'] = getstr(trim($_POST['pagetitle']), 255);
保存上传,立刻生效。
方案 B 的几个注意点:
- 仅修复门户文章。论坛主题、日志的截断逻辑分别在其他文件,需要分别修改:
source/include/post/post_newthread.php、source/include/spacecp/spacecp_blog.php等。 - 升级覆盖风险。Discuz 下一次小版本更新会把
portalcp_article.php覆盖回去,需要在每次升级后重新改一遍,或者把改动写成一个 hook 插件以避免被覆盖。 - 编码必须 UTF-8 无 BOM。Windows 下用记事本另存为容易加 BOM,导致 PHP 报 "headers already sent" 之类的错误,强烈建议用 VS Code、Notepad++ 等明确指定编码的编辑器。
- 修改前先备份。
cp portalcp_article.php portalcp_article.php.bak一份,回滚成本最低。
五、X3.5 升级踩坑回顾
保哥这次从 X3.4 升级到 X3.5 一共踩了 5 个坑,顺便记录一下避免后人重蹈覆辙:
- 覆盖式升级会清掉模板自定义。如果你直接把 X3.5 的全部文件解压覆盖到老站根目录,
template/default/下的自定义模板会被覆盖。务必先把template目录单独备份。 - UCenter 同步配置丢失。X3.5 把 UCenter 的部分配置项搬到了新位置,升级后第一件事是检查
config/config_ucenter.php是不是还指向正确的 UC_API。 - 插件兼容。一些 X3.4 时代的插件(特别是涉及到
showmessage()、getsubject()等核心函数的)在 X3.5 上需要重新打补丁,建议升级前到插件作者页面看 X3.5 适配版本。 - HTTPS 跳转。X3.5 加强了 HTTPS 处理,老站如果之前是 HTTP/HTTPS 混合状态,升级后可能出现大量"混合内容"警告,需要把站点配置里
$_config['cookie']['cookiedomain']等条目同步更新。 - maxsubjectsize 截断(本文重点)。这是覆盖升级带来的最隐蔽的体验回退之一。
建议升级前完整备份数据库 + 全部代码文件,备份脚本:
tar czf discuz_backup_$(date +%Y%m%d).tar.gz /www/wwwroot/your-discuz/
mysqldump -uroot -p discuz_db > discuz_db_backup_$(date +%Y%m%d).sql
有了备份回滚成本就低,遇到任何升级翻车都能 5 分钟内恢复到升级前状态。
六、相关配置项的连带影响
把"标题最大字数"从 80 调到 255 之后,建议同步检查以下相关配置:
- SEO 标题长度:搜索引擎对
<title>标签显示长度大约是 60 个英文字符 / 30 个汉字,超出会被搜索结果截断。即使后台允许写到 255 字,实战中建议鼓励用户把核心关键词放在前 30 字以内。 - 列表页样式:标题变长后,门户首页、文章列表的卡片可能撑出布局。建议在 CSS 里给标题加
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;,让前端层做截断显示,而不是数据库层硬截。 - RSS 输出:Discuz 默认 RSS 是把标题原样输出,长标题在 RSS 阅读器里可能显示异常,可以在
source/include/rss/rss_view.php里加一道前端层截断。 - 用户主题搜索:搜索功能内部对长标题的索引性能影响极小,但全文搜索引擎(XunSearch、Elasticsearch)需要重建索引。
- SEO Sitemap 生成:很多 Discuz 站点用插件生成 sitemap,长标题会让 sitemap 文件体积上升,记得检查插件是否对长标题有截断处理。
七、为什么默认设 80 而不是 255
有同学好奇 Discuz 为什么默认值是 80。保哥跟几个老 Discuz 开发者聊过,原因主要是历史遗留:
- 早期数据库性能:MySQL 5.0 时代
varchar(255)在某些字符集下会触发额外的 padding 开销,把限制压低到 80 能减少表空间。 - UI 设计:Discuz 的列表样式是为短标题设计的,长标题会破坏布局。默认 80 是"列表最多两行不换行"的安全值。
- SEO 友好:80 字标题正好覆盖搜索结果摘要的可视范围,超过这个长度的标题对 SEO 边际收益递减。
- 用户体验:用户输入长标题往往是关键词堆砌,限制长度反而能促使用户写更精炼的标题。
所以保哥的建议是:除非你确定业务真的需要长标题(比如一些专业知识库类站点),否则保留 80 默认值反而对站点整体质量更友好。如果你只是为了消掉省略号,可以把限制改为 100~120 字,既能避免大多数标题被截,又能维持基础规范。
八、防止升级被覆盖的插件化方案
如果你不打算用后台 UI 配置(方案 A),又怕方案 B 改源码被升级覆盖,可以做成 hook 插件。Discuz 的 plugin 机制允许在不改核心文件的前提下注入逻辑:
// source/plugin/your_plugin/portalcp_article.inc.php
class plugin_your_plugin_portalcp_article {
public function global_filter_title($param) {
// 在 getstr 截断前拦截,绕过 maxsubjectsize 限制
global $_G;
$_G['setting']['maxsubjectsize'] = 255;
return $param;
}
}
然后在后台 → 应用 → 插件,新建插件并指定钩子 portalcp_article_global_filter_title。这样升级核心文件时插件目录不会被覆盖,配置一次永久生效。
这个方案适合管理多个 Discuz 站点的运维同学,把这一类小修补全部插件化,迁移和升级都更省心。
九、最佳实践与最终建议
把上面所有方案串起来,保哥给出的优先级排序是:
- 第一优先:后台 → 全局 → 用户权限 → 标题最大字数,改成 120 或 255。这是 99% 场景的最佳解。
- 第二优先:如果后台找不到选项(早期 X3.5 版本),先升级到最新小版本(X3.5 R20240101 之后都已经修复 UI)。
- 第三优先:实在不能升级且必须改的,用 hook 插件方案保住升级安全。
- 不推荐:直接改源码方案 B 仅作应急用,每次升级后都要重新打补丁,长期维护成本高。
同时建议把"标题最大字数"列入升级 Checklist,每次 Discuz 版本升级后立刻验证一次门户发布、长标题保存是否正常,避免下次再被同样的坑卡几小时。
十、Discuz 标题截断逻辑深度解析
很多同学只是把 80 改成 255 就完事,其实理解一下 Discuz 内部是怎么处理标题的,能帮你在更复杂的场景里举一反三。保哥这里把核心逻辑讲清楚。
Discuz 的 getstr() 函数承担了非常多职责,远不止"截断字符串"。打开 source/function/function_core.php 可以看到,这个函数其实做了五件事:第一是 HTML 实体编码(htmlspecialchars 风格),第二是字符长度截取,第三是省略号追加,第四是反斜杠转义控制,第五是敏感词过滤(censor 参数)。当你只是想"标题别被截断"时,看似只需要改第二步,实际上整个函数链都在工作。
截断的核心是 cutstr() 子函数,它根据传入的 $length 参数和当前字符集(一般是 utf-8)逐字符判断字节宽度,超过阈值就插入 …。这里有个被忽视的细节:utf-8 编码下中文字符占 3 字节,英文字符占 1 字节,所以"字符数"和"字节数"是两码事。Discuz 内部默认是按"字符数"计算(也就是 80 个汉字 = 80 字符 = 240 字节),但在某些早期分支里曾经按字节数计算,迁移老站时偶尔能碰到这个坑。
另一个值得关注的点是,maxsubjectsize 还会被用来限制其他地方:
source/include/post/post_newthread.php里发新主题的标题校验。source/include/spacecp/spacecp_blog.php里日志标题校验。source/include/group/group_thread.php里群组主题校验。- 插件里很多第三方代码也读这个值做自己的截断逻辑。
所以"修改 maxsubjectsize"这一个动作,其实在做"全站标题长度策略"的一次性调整。这也是为什么保哥强烈推荐方案 A 后台 UI 改——一处改动全站统一生效,不用分别去每个文件打补丁。
十一、与 SEO 工作流的整合
Discuz 站点的 SEO 主战场是搜索词、长尾词与论坛主题集群,标题字数限制对 SEO 的影响主要在以下几个层面:
第一层:title 标签的有效长度。Google 搜索结果页的 <title> 显示宽度大约是 600 像素,对应中文约 30 字。即使后台允许 255 字标题,搜索结果展示出来后超过 30 字部分就会被搜索结果页截断成省略号。所以"后台 255 字限制"和"前端 SEO 友好"是两件事,需要分开优化。
第二层:列表页 H2/H3 长度。门户首页、文章列表里的标题往往用 <h2> 或 <h3> 渲染,CSS 控制单行显示。如果你允许 255 字标题,但 CSS 用的是 white-space: nowrap; overflow: hidden;,前端依然会截断显示——只是数据库里完整保存了。这种"数据库长 + 前端短"的策略最稳。
第三层:RSS / API 输出。Discuz 的 RSS 输出和 JSON API 都是直接读数据库 title 字段。如果你把字段放长了,记得给 RSS 模板加一道前端层 substr 截断,避免在 RSS 阅读器里显示异常。
第四层:sitemap 生成。多数 sitemap 插件不输出 title 字段,影响不大。但如果你用的是带 title 的特殊 sitemap 协议(比如新闻 sitemap),需要注意 Google 对 news sitemap 的 title 长度限制是 80 个字符。
第五层:全文搜索引擎索引。XunSearch、Elasticsearch 等外接搜索引擎对长 title 字段没有长度上限,但建议分配一个独立 field 而不是跟正文挤在一起,便于做 boost 加权。
十二、用户教育与编辑器层校验
从根本上来讲,与其无声地截断标题,不如在用户输入时就给提示。保哥推荐的做法是:
在门户文章发布页的 JavaScript 里加一个实时计数器:
<script>
const $title = document.querySelector('input[name=title]');
const $counter = document.createElement('span');
$counter.style.cssText = 'margin-left:10px;color:#999;';
$title.parentNode.appendChild($counter);
$title.addEventListener('input', () => {
const len = $title.value.length;
$counter.textContent = `${len} / 80`;
$counter.style.color = len > 80 ? '#e74c3c' : len > 60 ? '#f39c12' : '#27ae60';
});
</script>
这样用户输入到 60 字时变橙色提醒,超过 80 字变红,提前知道会被截断而不是发布后才发现。如果你的站点是技术爱好者用户多,他们会感谢你提供这种细节。
更进一步可以在前端层主动阻止超长输入:用 maxlength="80" 属性,浏览器会直接限制输入框最长字符数。但要注意 maxlength 计算的是字符数,对中英文混排可能有差异,复杂场景下还是用 JavaScript 监听 input 事件再处理更可靠。
十三、不同 Discuz 分支版本的差异
Discuz 在过去十几年里出现过多个分支:康盛官方版(X3.x)、社区维护版(如 Discuz! Q)、第三方分叉(FreeDz、HuaiYi 改版等)。这些分支对 maxsubjectsize 的处理略有不同,迁移或合站时需要留意:
- Discuz X3.5 官方版:标题最大字数后台可配,本文方案 A 适用。
- Discuz X3.4 及更早:写死 80,需要走方案 B 改源码。
- Discuz! Q(已停更):界面完全重构,
maxsubjectsize在config/console.json里以 JSON 形式配置,跟 X3.x 完全不同。 - FreeDz 系列:基本继承 X3.x 的代码结构,本文方案大多通用,但具体行号可能略有偏移。
- HuaiYi 改版:在 X3.x 基础上做了大量 UI 重写,但底层的
getstr()函数没动,方案 A 同样可用。
跨版本迁移时建议先把所有现有标题导出(SELECT title FROM pre_portal_article_title;),看看实际有多少标题超过新版本的默认上限,预先决定是抬升上限还是手动改写部分超长标题,能避免迁移后批量出现截断显示。
常见问题解答
Q1:改完之后老文章的省略号还在怎么办?
已经被截断的标题不会自动恢复——因为数据库里存的就是带省略号的版本。需要手动 SQL 批量清理:UPDATE pre_portal_article_title SET title = REPLACE(title, '…', '') WHERE title LIKE '%…%';。注意先备份数据库,省略号字符在不同字符集下编码可能不同,必要时改成具体的 unicode 码点匹配。
Q2:标题最大字数能调到 255 以上吗?
不能直接调。Discuz 数据库表 pre_portal_article_title.title、pre_forum_thread.subject 字段类型都是 varchar(255),超过 255 字符会被 MySQL 截断。如果业务真的需要更长,得手动 ALTER TABLE 把字段类型改成 varchar(500) 或 text,并同步修改后台配置项的上限校验逻辑。
Q3:改了后台配置但前端还是省略号?
大概率是缓存问题。Discuz 用文件缓存保存配置项,进入后台 → 工具 → 更新缓存 → 全部更新,再清一次浏览器缓存。如果还不行,检查 data/cache/setting.php 文件里的 maxsubjectsize 值是否已经更新到新数值,没更新就手动删掉这个文件让 Discuz 重建。
Q4:除了门户和论坛,群组、问答、家园日志会受影响吗?
会。maxsubjectsize 是全局变量,所有用 getstr() 函数处理标题的地方都会受影响,包括群组主题、日志、问答、记录、状态。后台改完一次,全站统一生效。
Q5:管理员发的标题也被截断了,怎么解?
管理组在论坛模块可以通过用户组设置里的"发帖不受限制"权限绕过截断,但门户和日志没有这个豁免。如果你希望管理组在门户也不受限,需要在 portalcp_article.php 里加一段判断:检测到当前用户在管理组($_G['groupid'] == 1)就跳过 getstr 调用。
Q6:改这个值会影响 Discuz 升级吗?
不会。后台 → 全局 → 用户权限 的配置存在数据库 pre_common_setting 表里,升级核心文件不会清空数据库设置。只有方案 B 直接改源码会被升级覆盖。
Q7:Discuz X3.4 用户能不能用同样方法?
X3.4 没有 maxsubjectsize 全局配置项,需要走方案 B 直接改源码,把 portalcp_article.php 里写死的 80 改成你想要的值。等到从 X3.4 升级到 X3.5 后再切到方案 A 即可。
把这个坑彻底填平后,Discuz X3.5 站点的门户体验就能回到正常水平。下次保哥会接着写 Discuz 升级到 X3.5 的完整 Checklist,敬请期待。