2026/10/22 8:21:57

帝国CMS新闻模型内容付费隐藏:模板截断与接口取全文实战

帝国CMS新闻模型内容付费隐藏:模板截断与接口取全文实战 简介面向帝国CMS站长与开发者的内容付费增强包专门解决新闻模型默认不支持部分内容付费阅读的问题。通过后台权限设置与会员等级联动可让普通用户浏览摘要付费或特定等级会员解锁全文广泛适用于在线教育、付费资讯、知识付费专栏等变现场景。资源包共20个文件压缩后仅120KB部署非常轻量文件以5个PHP核心逻辑、4个JS交互脚本、3个CSS样式、3个GIF与2个PNG图标为主同时包含后台执行SQL语句、输入表单替换HTML代码和Ecmsshop插件安装说明等关键文档能快速理清改造步骤。目前已有554人学习下载适合具备帝国CMS基础、希望低成本实现内容变现的中级开发者。借助SQL脚本可标记付费字段并同步会员权限再结合输入表单嵌入注册/支付入口配合e支付接口完成在线付款后自动解锁内容Ecmsshop插件安装说明则进一步简化了支付流程配置整体方案既能保护原创内容又能有效提升网站会员转化率与盈利能力。1. 帝国CMS新闻模型付费查看为什么不能只改模板做帝国CMS新闻模型部分内容隐藏付费查看最容易翻车的不是支付接口而是正文的输出方式。很多人改完模板本地测试点按钮能弹窗、能跳支付一上线就露馅游客按F12看源码后半篇内容原封不动躺在HTML里或者站点开了静态生成付费墙跟着页面一起被固定所有访客看到的内容一模一样。问题不出在“有没有付费”而出在“内容从哪一段开始真正隐藏”。这个标题真正要落地的是四件事新闻模型正文按比例截断、会员免费查看规则、金币扣费、购买记录防重复。四件事是同一套流程不是改一个模板标签就能收工。比较适合做付费资讯、小说连载、行业资料类站点的站长和二次开发下面按“路径选型、参数配置、接口流程、避坑”顺序讲清楚。2. 部分内容隐藏的三种实现双字段、前端遮罩与接口取全文2.1 双字段拆分编辑手动切稳但费人帝国CMS新闻模型自带 newstext 字段最直接的思路是再新建一个长文本字段比如 paytext发布新闻时免费部分放正文付费部分放 paytext内容页模板按用户状态判断输出哪一段。这个方案的优点是源码极其干净隐藏内容根本没有出现在 HTML 里搜索引擎收录的永远是免费部分彻底不用考虑“内容泄露”。缺点是编辑要手动把一篇稿子切成两段每天更新几十篇内容时工作量很大而且切错了段落读者看到一半就断掉体验很差。比较适合日更新量小、内容质量高、单篇字数多的站点。2.2 前端截断加遮罩最快但源码全部暴露第二种常见做法是正文完整输出CSS 遮罩层盖住后半段点击按钮后去掉遮罩或跳转支付。实现成本最低三分钟就能改完但隐藏内容完全在页面源码里任何访客都能直接复制。对搜索引擎来说全文都被收录等于付费墙形同虚设。这个方案我只建议用在内部测试、演示环境或者内容价值不高的站。真实业务如果这么做运营迟早会因为内容被搬运而被迫重做一遍属于走了弯路。2.3 按比例截断加接口取全文推荐路线这次采用的方案是模板输出时用 PHP 截取正文的前 N%生成静态页时截断结果直接固化源码里不存在后半段用户点击解锁后前端 AJAX 请求一个独立 PHP 接口接口判断登录状态、会员组、购买记录扣费后返回完整正文JS 再把全文替换进页面。三条路径对比如下对比项双字段拆分前端遮罩截断接口取全文实现成本低但编辑成本高最低中需写接口隐藏内容是否在源码不在在不在静态发布兼容性兼容兼容兼容防复制效果好差好会员免费查看配置模板判断模板判断接口判断是否适合规模化更新不适合适合但无意义适合选择第三条路线还有一个关键理由判断逻辑放在接口里静态生成的 HTML 页面不依赖用户态这样帝国CMS 的静态发布机制不会破坏付费墙。接下来所有配置和代码都基于这条路。3. 配置文件与购买记录表把会员免费查看和隐藏比例做成可设置参数3.1 新建配置文件隐藏比例、解锁价格、免费会员组先说清楚一个设计决定不把参数塞进帝国CMS后台的系统参数表而是独立放一个配置文件。原因是后台的系统参数设置不支持随意增加自定义键而且每次改参数都要进后台点保存对开发调试来说反而不如直接改文件来得直观。在 e/data/ 目录下新建 news_pay.php内容如下?php // 帝国CMS新闻模型付费查看配置 return array( // 正文前多少百分比免费展示整数范围 1-99 rate 60, // 解锁剩余内容所需金币数 fee 10, // 免费查看的会员组ID列表1游客组2普通会员组按自己站后台实际ID调整 freeGroup array(2, 3), // 接口文件名前端AJAX请求用 apiUrl /e/action/newspay.php ); ?参数说明rate 是隐藏比例写作 60 意味着正文前 60% 免费后 40% 需要解锁。fee 是扣费金币数与帝国CMS后台会员金币体系挂钩。freeGroup 数组里放的是会员组ID后台在“会员管理 - 会员组”里可以看到默认情况下 1 是游客2 是普通会员3 可能是其他自定义组。把某个组ID放进去该组用户请求接口时直接返回全文不扣费。注意游客组默认不走免费逻辑否则等于免费公开。需要游客也能看全文的站点可以手动在数组里加 1但一般不建议。3.2 自建购买记录表防止重复扣费和重复购买帝国CMS本身没有“按信息维度记录购买”的现成表商城的订单表结构太重且和新闻模型没有直接关联。这里自建一张轻量购买记录表把每次解锁行为记录进去接口判断时先查这张表。CREATE TABLE IF NOT EXISTS phome_news_paylog ( id int(11) NOT NULL AUTO_INCREMENT, userid int(11) NOT NULL DEFAULT 0 COMMENT 用户ID, newsid int(11) NOT NULL DEFAULT 0 COMMENT 新闻ID, classid int(11) NOT NULL DEFAULT 0 COMMENT 栏目ID, type tinyint(1) NOT NULL DEFAULT 0 COMMENT 0免费组阅读 1金币购买, money int(11) NOT NULL DEFAULT 0 COMMENT 扣费金币数, time int(11) NOT NULL DEFAULT 0 COMMENT 购买时间戳, PRIMARY KEY (id), KEY userid (userid), KEY newsid (newsid) ) ENGINEMyISAM DEFAULT CHARSETutf8;表名前缀 phome_ 需要替换成自己站点的实际表前缀后台的“系统设置 - 参数设置”里能看到。type 字段区分“免费组阅读”和“金币购买”将来做数据统计时可以直接按 type 分组知道多少内容是免费会员组看的多少是付费买的。这张表不记录正文内容本身只记录“谁在什么时候解锁了哪篇信息”。正文始终从新闻主表读取避免两份数据不同步的问题。3.3 新闻模型内容页模板改造截断输出加解锁按钮内容页模板的修改是整个方案里最容易踩坑的部分先说原理帝国CMS模板支持直接写 PHP 块生成静态页时这些 PHP 块会执行一次生成后的 HTML 保留执行结果。所以模板里执行“读正文、截断、输出前半段”这个动作生成出来的静态页源码里就只有前半段没有全文。在内容页模板的正文位置写一个条件替换块去掉原来的 [!--newstext--] 标签换成以下代码?php $paycfg include ECMS_PATH.e/data/news_pay.php; // 当前新闻ID和栏目ID模板编译执行时可用 $nid intval($id); $nclass intval($classid); // 从新闻主表读取正文 $nr $empire-fetch1(SELECT newstext FROM {$dbtbpre}ecms_news WHERE id$nid LIMIT 1); $fulltext $nr[newstext]; // 统计纯文本长度按比例截断保留前 rate% 部分 $clean preg_replace(/script[\s\S]*?\/script/i, , $fulltext); $clean preg_replace(/style[\s\S]*?\/style/i, , $clean); $totalLen mb_strlen(strip_tags($clean), utf-8); $targetLen intval($totalLen * $paycfg[rate] / 100); $show ; $pos 0; $len 0; while ($len $targetLen preg_match(/[^]|[^]/, $clean, $m, PREG_OFFSET_CAPTURE, $pos)) { $part $m[0][0]; $pos $m[0][1] strlen($part); if (preg_match(/^[^]$/, $part)) { $show . $part; } else { $need $targetLen - $len; if (mb_strlen($part, utf-8) $need) { $show . $part; $len mb_strlen($part, utf-8); } else { $show . mb_substr($part, 0, $need, utf-8); $len $targetLen; } } } echo $show; ? div classpay-lock>?php require(../class/connect.php); require(../class/db_sql.php); require(../class/config.php); require(../data/news_pay.php); $link db_connect(); $empire new mysqlquery(); header(Content-Type: application/json; charsetutf-8); $id intval($_POST[id]); $classid intval($_POST[classid]); if ($id 0) { echo json_encode(array(code 400, msg 参数错误)); exit; } $uid intval(getcvar(mluserid)); $gid intval(getcvar(mlgroupid)); $paycfg include ECMS_PATH.e/data/news_pay.php; // 第一步未登录直接提示 if (empty($uid)) { echo json_encode(array(code 401, msg 请先登录后再解锁)); exit; } // 第二步免费会员组直接放行 if (in_array($gid, $paycfg[freeGroup])) { $r $empire-fetch1(SELECT newstext FROM {$dbtbpre}ecms_news WHERE id$id LIMIT 1); $empire-query(INSERT INTO {$dbtbpre}news_paylog (userid,newsid,classid,type,money,time) VALUES ($uid,$id,$classid,0,0,.time().)); echo json_encode(array(code 200, content $r[newstext])); exit; } // 第三步查购买记录已购买直接返回全文 $log $empire-fetch1(SELECT id FROM {$dbtbpre}news_paylog WHERE newsid$id AND userid$uid LIMIT 1); if ($log[id]) { $r $empire-fetch1(SELECT newstext FROM {$dbtbpre}ecms_news WHERE id$id LIMIT 1); echo json_encode(array(code 200, content $r[newstext])); exit; } // 第四步检查金币余额 $user $empire-fetch1(SELECT user_metal FROM {$dbtbpre}enewsuser WHERE userid$uid LIMIT 1); if (intval($user[user_metal]) intval($paycfg[fee])) { echo json_encode(array(code 403, msg 金币不足请先充值)); exit; } ?执行顺序很重要先判断免费组再判断已购买最后才查余额。如果顺序反过来免费组用户也会走扣费逻辑造成误会。已购买判断放在余额判断前面避免老用户每次都走扣费流程。4.2 扣费操作条件UPDATE防重复扣款扣费最怕的情况是用户连续点击按钮两个请求同时到达结果扣了两次钱。解决思路是把“余额检查和扣款”合并成一条条件UPDATE语句让数据库在扣款时自己判断余额是否足够。?php // 接上面未结束的逻辑条件扣款 $fee intval($paycfg[fee]); $empire-query(UPDATE {$dbtbpre}enewsuser SET user_metal user_metal - $fee WHERE userid$uid AND user_metal $fee); $affect $empire-affected_rows(); if ($affect 1) { echo json_encode(array(code 403, msg 金币不足请先充值)); exit; } // 扣款成功才写购买记录 $empire-query(INSERT INTO {$dbtbpre}news_paylog (userid,newsid,classid,type,money,time) VALUES ($uid,$id,$classid,1,$fee,.time().)); // 返回全文 $r $empire-fetch1(SELECT newstext FROM {$dbtbpre}ecms_news WHERE id$id LIMIT 1); echo json_encode(array(code 200, content $r[newstext])); exit; ?上面的 UPDATE 语句中WHERE 条件同时带上了 user_metal $feeMySQL 在执行时只有余额足够才会真正更新。$empire-affected_rows() 返回受影响行数如果余额不足UPDATE 匹配不到记录影响行数是 0接口就知道不能扣费了。购买记录的 INSERT 一定要放在 UPDATE 成功之后否则会出现“记录扣了钱但没写日志”的脏数据。顺序错了用户刷新页面就会看到重复扣费记录。4.3 前端JSAJAX请求接口并替换内容内容页的解锁按钮不需要跳转任何链接直接用 AJAX POST 请求接口。页面是静态生成也不影响因为 ID 和栏目ID 已经固化在按钮的 data 属性里了。(function() { var lock document.querySelector(.pay-lock); if (!lock) return; var btn lock.querySelector(.pay-btn); var id lock.getAttribute(data-id); var classid lock.getAttribute(data-cid); btn.addEventListener(click, function() { if (btn.classList.contains(loading)) return; btn.classList.add(loading); btn.textContent 处理中...; var xhr new XMLHttpRequest(); xhr.open(POST, /e/action/newspay.php, true); xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded); xhr.onreadystatechange function() { if (xhr.readyState 4 xhr.status 200) { var res JSON.parse(xhr.responseText); if (res.code 200) { lock.outerHTML div classpay-full res.content /div; } else { alert(res.msg); btn.classList.remove(loading); btn.textContent 解锁全文; } } }; xhr.send(id id classid classid); }); })();这段 JS 在解锁成功后用返回的完整内容替换整个遮罩块替换后页面里就是未截断的正文。如果返回 code 不是 200弹出提示并恢复按钮状态。loading 类用于防止用户连点虽然接口层已经有条件UPDATE兜底前端再加一层防连点更稳妥。5. 帝国CMS付费隐藏的5个避坑记录静态页、HTML截断、重复扣费5.1 静态生成后付费墙完全失效现象内容页开启静态发布后生成的 HTML 里付费按钮和截断内容全都没了或者所有访客看到同一段全文。原因帝国CMS静态生成时模板里的 PHP 块按生成时的条件执行并固化结果。如果当时把“付费判断”写进了模板生成的 HTML 就固定了。解决判断逻辑全部收敛到接口模板只做“截断输出前半段 输出按钮ID”不输出任何依赖用户态的内容。上面第 3、4 章的代码已经按这个思路设计照搬即可。手动确认方法生成静态页后用浏览器看源码全文不应出现在 HTML 里。5.2 截断时把HTML标签切断页面错乱现象截断后的内容前端显示一大片乱码或者 div 没闭合页面布局整体崩掉。原因直接按字数 mb_substr 截断把、这类标签从中间剪开浏览器解析时无法闭合。解决按第 3.3 节的扫描方式处理HTML 标签原样保留只在文本节点上做长度累计。如果截断点落在标签内部也不做强行分割。这段代码里用的是边扫描边输出的写法对常见的嵌套标签已经够用极端情况可以配合 Tidy 库做二次清洗。5.3 隐藏内容出现在页面源码里现象解锁前按 F12 查看源码后半段文字明文可见。原因用了前端遮罩方案或者模板里直接输出 [!--newstext--] 再用 JS 隐藏。隐藏只是视觉效果源码里内容还在。解决改用接口取全文模板只输出截断结果。也可以用双字段方案让编辑手动切但维护成本更高。检查动作要成为上线习惯每次改完模板生成静态页后看一次源码。5.4 用户没登录就点解锁支付后看不到全文还重复扣费现象游客点击解锁被引导登录登录后再点一次结果扣了两次金币才看到内容。原因游客态请求接口时没有记录可写登录后等于重新走了一遍扣费流程。接口里缺少“登录后查购买记录”的步骤。解决4.1 节的第三步接口要在扣费前查 news_paylog 表有记录直接返回全文。注意这个查询必须用 userid 和 newsid 两个条件少一个条件就会把别的用户的购买记录匹配过来。5.5 按字符比例截断导致不同文章展示比例不一致现象有的文章公开了 70% 视觉内容有的文章只公开了 30%明明 rate 都配的 60。原因正文里的 HTML 标签、图片 alt 文本、空格换行都被算进了字符数。文章代码密集程度不同比例差异就很大。解决第 3.3 节代码里已用 strip_tags 统计纯文本长度并在文本节点上截断。换算逻辑是“全文可见字符数 × rate/100”标签不占额度。短内容建议再加一个保底逻辑如果文章总字符数少于一定值直接全额公开避免用户看到只有两行的残文。6. 进阶验证蜘蛛UA返回全文、并发防超卖与内容混淆返回接口上线后用命令行验证三个场景。模拟蜘蛛抓取确认搜索引擎抓到的内容和用户解锁前看到的一致curl -A Baiduspider -d id1classid1 http://你的域名/e/action/newspay.php curl -A Mozilla/5.0 -d id1classid1 http://你的域名/e/action/newspay.php两条命令分别模拟蜘蛛UA和普通浏览器UA返回体应该一致。如果决定对蜘蛛放行全文需要在接口里加 UA 判断在第二步免费组判断之后输出全文如果决定统一隐藏就什么都不用改。我个人倾向于统一隐藏因为搜索引擎收录了全文就等于内容公开付费墙意义归零收录免费段足够引流。并发防超卖方面4.2 节的条件 UPDATE 已经能挡住绝大多数重复扣费但如果站点做了读写分离、或者把数据表引擎换成了 InnoDB可以在扣费前手动加表锁LOCK TABLES phome_enewsuser WRITE, phome_news_paylog WRITE; -- 执行查询与扣费 UNLOCK TABLES;表锁在多台应用服务器同时请求时会排队性能差但数据稳。日常小站不需要这一步只有做秒杀式内容活动时才值得。最后一个技巧是内容混淆返回。接口返回的 content 直接拼接进 HTML抓包的人能直接看到全文。可以先用 PHP 做一层简单转码前端解码后再插入echo json_encode(array(code 200, content base64_encode(gzencode($r[newstext], 6))));var raw res.content; var decoded decodeURIComponent(escape(atob(raw))); // gzip 解压需要引入 pako 之类的库这里再处理一层这个方案挡不住真正懂行的人但能拦住批量抓内容的脚本。数据本身在服务端是明文任何加密都只是时间成本真正的防抓取还是靠接口频率限制和登录校验。我自己的上线习惯是每次改完模板先去后台清一次缓存再生成对应内容页静态文件然后开浏览器无痕模式验证游客视图再登录一个免费会员组账号验证免扣费视图最后看一次页面源码确认截断生效。这套动作用不了五分钟但能挡住上面 80% 的翻车现场。希望帮到你。本文还有配套的精品资源点击获取