Discuz! X3 二次开发核心教程
第一部分:准备工作与核心概念
在开始编码之前,你必须先理解 Discuz! 的一些核心概念和准备工作。
环境准备
你需要一个标准的 PHP 开发环境:
- Web 服务器: Nginx 或 Apache
- 数据库: MySQL 5.0+ 或 MariaDB
- PHP: PHP 5.3+ (推荐 5.4 或 5.5,高版本可能不兼容)
- Discuz! X3 源码: 从官方或可靠渠道获取。
- 开发工具: VS Code, Sublime Text, PhpStorm 等。
目录结构初探
了解 Discuz! 的目录结构是二次开发的第一步,核心目录如下:
source/: 核心代码目录,所有的业务逻辑、数据处理都在这里。class/: 核心类库文件,如discuz类(全局核心类)、table类(数据表操作基类)等。module/: 模块目录,按功能模块划分,如forum/(论坛),home/(个人空间),admin/(后台管理)。function/: 全局函数库,包含大量常用函数。
static/: 静态资源目录,存放 CSS, JavaScript, 图片等文件。template/: 模板目录,存放所有页面的 HTML 模板文件。api/: 相关接口文件。uc_client/,uc_server/: 用户中心相关代码。config/: 配置文件目录,最重要的config_global.phpinstall/: 安装程序目录。
核心概念
-
入口文件:
index.php,所有前台页面的请求都通过这个文件进入,它会加载核心类,并根据 URL 参数决定调用哪个模块和函数。 -
全局核心类 -
Discuz:source/class/discuz/discuz.class.php,这是 Discuz! 的“心脏”,负责初始化整个应用、加载配置、处理用户会话、权限验证等,几乎所有页面都会先实例化这个类。 -
$_G全局变量:$_G是一个超级全局数组,存储了当前运行环境的所有核心信息,如:$_G['uid']: 当前登录用户 ID。$_G['username']: 当前登录用户名。$_G['group']: 当前用户组信息。$_G['config']: 站点配置信息。$_G['forum']: 当前论坛相关信息。- 你可以通过
$_G在任何地方获取站点的全局状态。
-
数据库操作 -
DB类: Discuz! 封装了DB类用于数据库操作,你不需要写原生 SQL,而是使用其提供的链式操作。-
示例:
// 从 pre_common_member 表中查询 uid=1 的用户 $member = DB::fetch_first("SELECT * FROM %t WHERE uid=%d", array('common_member', 1)); // 使用链式操作(推荐) $member = DB::fetch_first("SELECT * FROM %t WHERE uid=%d", array('common_member', 1)); // 或者 $member = DB::table('common_member')->where('uid', 1)->get();
-
-
缓存机制: Discuz! 使用了
cache类来处理缓存,能极大提升性能,缓存文件通常位于data/cache/目录下。C::t('tablename')->fetch_by_...()等方法在读取数据时,会自动检查缓存。
-
钩子 -
plugin: Discuz! 的插件系统是其扩展性的核心,它通过在特定位置设置“钩子”,允许插件代码在这些位置被执行,开发插件就是编写能挂载到这些钩子上的代码。
第二部分:实战开发 - 创建一个简单的“Hello World”页面
我们将通过创建一个自定义页面,来熟悉 Discuz! 的开发流程。
目标: 在 你的域名/hello 访问到一个显示 "Hello, Discuz! X3!" 的页面。
步骤 1: 创建控制器
控制器负责处理逻辑,我们将在 source/module/ 下创建一个自定义模块。
-
在
source/module/目录下新建一个文件夹,命名为mymodule。 -
在
source/module/mymodule/目录下,创建一个 PHP 文件,命名为hello.php。<?php /** * Discuz! X3 自定义模块示例 */ if(!defined('IN_DISCUZ')) { exit('Access Denied'); } // class mymodule_hello 是必须的命名规范,模块名_文件名 class mymodule_hello { // 构造函数,页面加载时自动执行 public function __construct() { // 可以在这里做一些初始化操作 } // 定义一个公共方法,方法名将作为 URL 中的 action 参数 public function index() { // 1. 准备数据 $message = 'Hello, Discuz! X3!'; // 2. 加载模板 // template/mymodule:hello 是模板路径,对应 template/mymodule/hello.htm include template('mymodule:hello'); } }
代码解释:
if(!defined('IN_DISCUZ')) exit('Access Denied');: 这是 Discuz! 的安全检查,防止文件被直接访问。class mymodule_hello: 类名必须遵循模块名_文件名的规则。public function index(): 这个方法将作为我们页面的默认逻辑,URL 中的action参数会对应方法名。
步骤 2: 创建模板
模板负责展示数据。
-
在
template/目录下新建一个文件夹,命名为mymodule。 -
在
template/mymodule/目录下,创建一个 HTML 文件,命名为hello.htm。<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>我的第一个 Discuz! 页面</title> </head> <body> <h1>{$_G['siteinfo']['name']} - 示例页面</h1> <p>{$_G['message']}</p> </body> </html>
代码解释:
{$_G['siteinfo']['name']}: 这是 Discuz! 的模板语法,用于输出全局变量$_G中的站点名称。{$_G['message']}: 我们将在控制器中把变量赋值给$_G,以便在模板中调用。
步骤 3: 修改控制器以传递数据
我们需要修改 hello.php,将 $message 变量传递给模板。
修改 source/module/mymodule/hello.php 中的 index() 方法:
public function index() {
// 1. 准备数据
$message = 'Hello, Discuz! X3!';
// 2. 将变量传递给模板,推荐使用 $_G
$_G['message'] = $message;
// 3. 加载模板
include template('mymodule:hello');
}
步骤 4: 访问页面
你可以通过浏览器访问 你的域名/index.php?mymodule-hello-index 来查看你的页面了。
index.php: 入口文件。mymodule: 模块名,对应source/module/mymodule/。hello: 控制器文件名,对应source/module/mymodule/hello.php。index: 控制器中的方法名。
如果一切正常,你应该能看到一个标题为你的站点名称,内容为 "Hello, Discuz! X3!" 的页面。
第三部分:进阶主题
数据库操作实战
假设我们要在页面上显示论坛的最新 5 个主题。
-
修改控制器 (
source/module/mymodule/hello.php):public function index() { // 从 pre_forum_post 表中查询最新的 5 个主题 // 注意:表名前缀 'pre_' 是动态的,使用 %t 可以自动替换 $query = DB::query("SELECT * FROM %t WHERE fid!=0 AND first=1 ORDER BY tid DESC LIMIT 5", array('forum_post')); $threads = array(); while ($thread = DB::fetch($query)) { $threads[] = $thread; } // 将主题数组传递给模板 $_G['threads'] = $threads; include template('mymodule:hello'); } -
修改模板 (
template/mymodule/hello.htm):<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>我的第一个 Discuz! 页面</title> </head> <body> <h1>{$_G['siteinfo']['name']} - 示例页面</h1> <p>{$_G['message']}</p> <hr> <h2>最新 5 个主题</h2> <ul> <!-- 使用 foreach 循环输出主题列表 --> {loop $_G['threads'] $thread} <li> <a href="forum.php?mod=viewthread&tid={$thread[tid]}" target="_blank"> {$thread[subject]} </a> - 发布于 {date($thread[dateline], 'Y-m-d H:i')} </li> {/loop} </ul> </body> </html>模板语法解释:
{loop $array $value}: 循环遍历数组。{$thread[tid]}: 输出数组元素的值。{date($timestamp, 'format')}: Discuz! 提供的日期格式化函数。
后台开发
后台开发与前台类似,但目录和权限不同。
- 目录: 后台控制器在
source/admincp/目录下。 - 入口: 后台入口是
admin.php。 - 权限: 后台操作必须检查管理员权限,通常通过
checkauth()和checkprima()函数实现。
简单示例:在后台添加一个菜单项和页面
-
修改菜单文件:
source/language/lang_admincp.php(或对应的语言包文件),在$_G['adminmenu']['plugin']数组中添加你的菜单项。'plugin' => array( 'name' => '插件', 'url' => 'admin.php?action=plugins', 'sub' => array( // ... 其他子菜单 array('name' => '我的模块', 'url' => 'admin.php?action=mymodule'), // 添加这一行 ), ), -
创建后台控制器: 在
source/admincp/下创建mymodule.php。<?php if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) { exit('Access Denied'); } class admincp_mymodule { public function run() { // 检查权限 if(!getstatus($_G['group']['allowadminplugin'], 5)) { // 假设权限位是第5位 cpmsg('plugin_nopermission', '', 'error'); } // 这里可以写后台逻辑,比如保存设置等 if(submitcheck('settingsubmit')) { // 处理表单提交... cpmsg('settings_updated', 'action=mymodule', 'succeed'); } // 加载后台模板 include template('admin/mymodule:settings'); } } -
创建后台模板: 在
template/admin/下创建mymodule_settings.htm。<form method="post" autocomplete="off" action="admin.php?action=mymodule"> <input type="hidden" name="formhash" value="{FORMHASH}" /> <input type="hidden" name="settingsubmit" value="1" /> <div class="bm bml"> <div class="bm_h cl"> <h2>我的模块设置</h2> </div> <div class="bm_c"> <!-- 这里可以放设置表单 --> <table class="tb tb2"> <tr> <th>一个示例设置</th> <td><input type="text" class="px" name="setting[example]" value="{$setting[example]}" /></td> </tr> </table> </div> </div> <div class="btnarea"> <button type="submit" class="pn pnc"><strong>提交</strong></button> </div> </form>
第四部分:调试与技巧
- 开启调试模式: 在
config/config_global.php中,将$_config['debug']设置为1或2,可以开启详细的错误报告和 SQL 查询日志,非常有助于调试。 - 使用
print_r和var_dump: 在代码中适当位置使用print_r('<pre>'); print_r($_G); print_r('</pre>');来查看变量内容。 - 阅读官方源码: 最好的学习方式就是阅读 Discuz! 自身模块的源码,
source/module/forum/下的文件,看看它是如何实现帖子列表、详情页等功能的。 - 利用缓存: 修改模板后,需要清除
data/template/下的缓存文件才能看到效果,修改配置或数据后,可能需要清除data/cache/下的缓存。
Discuz! X3 的二次开发虽然基于较老的技术,但其架构设计依然有其合理性,掌握其核心要点:
- 入口与路由:
index.php和module目录结构。 - 全局状态:
$_G变量的使用。 - 数据层:
DB类的链式操作。 - 表现层:
template目录和模板语法。 - 扩展性:
plugin钩子系统。
从简单的页面开始,逐步尝试数据库交互和后台功能,你很快就能上手 Discuz! X3 的二次开发了,祝你开发顺利!
