请求从开始到结束的完整路径详解
# Xiuno BBS 4.0 请求从开始到结束的完整路径详解
> 本文档是 `请求从开始到结束的完整路径.mmd` 的伴侣文件,详细解释 Xiuno BBS 4.0 中 HTTP 请求的完整生命周期。
> 本文档使用人工智能辅助编写。
## 概述
Xiuno BBS 4.0 采用经典的 MVC 架构模式,请求处理流程清晰明了。整个流程分为两个主要阶段:
1. **初始化阶段**:完成框架加载、配置读取、数据库连接等基础工作
2. **业务逻辑阶段**:根据请求路由到对应的处理逻辑,完成数据处理和视图渲染
---
## 初始化阶段
### 1. 用户发送请求
用户通过浏览器访问 Xiuno BBS 的任意页面,例如:
- 首页:`http://example.com/` 或 `http://example.com/index.php`
- 帖子详情:`http://example.com/index.php?thread-123.htm`
- 版块列表:`http://example.com/index.php?forum-5-1.htm`
所有请求都统一由 `index.php` 入口文件处理。
### 2. index.php 装载配置文件
**代码位置**:[index.php:16](file:///m:/laragon/www/xiunobbs-v4.0.7/index.php#L16)
```php
$conf = (@include APP_PATH.'conf/conf.php') OR exit('');
```
**关键操作**:
- 读取 `conf/conf.php` 配置文件
- 如果配置文件不存在,自动跳转到安装目录
- 兼容旧版本配置项(4.0.3)
**配置内容示例**:
```php
$conf = array(
'db' => array(
'type' => 'mysql',
'host' => 'localhost',
'name' => 'xiunobbs',
'user' => 'root',
'password' => 'password',
),
'cache' => array(
'type' => 'file', // 或 'memcache', 'redis'
),
'sitename' => '我的论坛',
'pagesize' => 20,
// ... 更多配置
);
```
### 3. 装载 Xiuno PHP 核心文件
**代码位置**:[index.php:32-36](file:///m:/laragon/www/xiunobbs-v4.0.7/index.php#L32-L36)
```php
if(DEBUG > 1) {
include XIUNOPHP_PATH.'xiunophp.php';
} else {
include XIUNOPHP_PATH.'xiunophp.min.php';
}
```
**关键说明**:
- `DEBUG` 模式决定加载完整版还是压缩版核心
- `DEBUG = 0`:生产模式,加载压缩版核心
- `DEBUG = 1`:调试模式,加载完整版核心
- `DEBUG = 2`:插件开发模式
**核心文件包含**:
- 数据库操作函数(db_connect, db_query 等)
- 缓存操作函数(cache_get, cache_set 等)
- 字符串处理函数
- 数组处理函数
- 安全过滤函数
### 4. 数据库连接
Xiuno PHP 核心加载时会自动根据 `conf.php` 中的数据库配置建立连接。
**连接时机**:
- 配置文件加载完成后
- 核心函数库加载时
- 延迟连接:实际使用时才建立连接(懒加载)
**数据库配置**:
```php
// conf/conf.php
$conf['db']['type'] = 'mysql';
$conf['db']['host'] = 'localhost';
$conf['db']['name'] = 'xiunobbs';
$conf['db']['user'] = 'root';
$conf['db']['password'] = 'password';
$conf['db']['charset'] = 'utf8';
$conf['db']['engine'] = 'innodb';
```
### 5. 插件功能初始化
**代码位置**:[index.php:41](file:///m:/laragon/www/xiunobbs-v4.0.7/index.php#L41)
```php
include APP_PATH.'model/plugin.func.php';
```
**关键功能**:
- 初始化插件机制
- 实现 **Overwrite(覆盖)机制**:允许插件覆盖原有文件
- 实现 **Hook(钩子)机制**:允许插件在特定位置插入代码
**核心函数**:
```php
// plugin.func.php 中的关键函数
function _include($filepath) {
// 检查是否有插件覆盖该文件
// 如果有,返回插件文件路径
// 否则,返回原文件路径
}
```
### 6. Model 层初始化
**代码位置**:[index.php:42](file:///m:/laragon/www/xiunobbs-v4.0.7/index.php#L42)
```php
include _include(APP_PATH.'model.inc.php');
```
**关键操作**:
- 加载所有 Model 层的函数文件
- Model 文件位于 `model/` 目录
- 每个数据表对应一个 Model 文件
**Model 文件列表**:
```
model/
├── user.func.php // 用户相关操作
├── thread.func.php // 主题相关操作
├── post.func.php // 帖子相关操作
├── forum.func.php // 版块相关操作
├── attach.func.php // 附件相关操作
├── group.func.php // 用户组相关操作
├── session.func.php // 会话相关操作
└── ... 其他 Model 文件
```
**Model 函数示例**:
```php
// model/user.func.php
function user_read($uid) {
// 从数据库读取用户信息
// 返回用户数组
}
function user_create($user) {
// 创建新用户
// 返回用户 ID
}
function user_update($uid, $data) {
// 更新用户信息
// 返回影响行数
}
```
### 7. 进入路由层
**代码位置**:[index.php:43](file:///m:/laragon/www/xiunobbs-v4.0.7/index.php#L43)
```php
include _include(APP_PATH.'index.inc.php');
```
**关键操作**:
- 加载 `index.inc.php` 文件
- 开始会话管理
- 初始化全局变量
- 进行路由分发
**index.inc.php 核心逻辑**:
```php
// 启动会话
$sid = sess_start();
// 加载语言包
$_SERVER['lang'] = $lang = include _include(APP_PATH."lang/$conf[lang]/bbs.php");
// 获取用户组列表
$grouplist = group_list_cache();
// 获取当前用户信息
$uid = intval(_SESSION('uid'));
empty($uid) AND $uid = user_token_get() AND $_SESSION['uid'] = $uid;
$user = user_read($uid);
// 获取用户组
$gid = empty($user) ? 0 : intval($user['gid']);
$group = isset($grouplist[$gid]) ? $grouplist[$gid] : $grouplist[0];
// 获取版块列表
$fid = 0;
$forumlist = forum_list_cache();
$forumlist_show = forum_list_access_filter($forumlist, $gid);
// 初始化运行时数据
$runtime = runtime_init();
// 检测站点运行级别
check_runlevel();
// 获取路由参数
$route = param(0, 'index');
```
---
## 业务逻辑阶段
### 8. 路由分发
**代码位置**:[index.inc.php:49-80](file:///m:/laragon/www/xiunobbs-v4.0.7/index.inc.php#L49-L80)
```php
$route = param(0, 'index');
if(!defined('SKIP_ROUTE')) {
switch ($route) {
case 'index':
include _include(APP_PATH.'route/index.php');
break;
case 'thread':
include _include(APP_PATH.'route/thread.php');
break;
case 'forum':
include _include(APP_PATH.'route/forum.php');
break;
case 'user':
include _include(APP_PATH.'route/user.php');
break;
case 'my':
include _include(APP_PATH.'route/my.php');
break;
case 'attach':
include _include(APP_PATH.'route/attach.php');
break;
case 'post':
include _include(APP_PATH.'route/post.php');
break;
case 'mod':
include _include(APP_PATH.'route/mod.php');
break;
case 'browser':
include _include(APP_PATH.'route/browser.php');
break;
default:
include _include(APP_PATH.'route/index.php');
break;
}
}
```
**路由规则**:
- 通过 `param(0)` 获取 URL 第一个参数作为路由标识
- 使用 switch-case 进行路由分发
- 默认路由为 `index`(首页)
**URL 与路由对应关系**:
```
http://example.com/ → route/index.php
http://example.com/index.php → route/index.php
http://example.com/thread-123.htm → route/thread.php
http://example.com/forum-5-1.htm → route/forum.php
http://example.com/user-login.htm → route/user.php
http://example.com/my.htm → route/my.php
```
### 9. 子路由判断
每个路由文件内部会根据 `param(1)` 等参数判断具体的子路由。
**示例:route/user.php 的子路由**
**代码位置**:[route/user.php:7-404](file:///m:/laragon/www/xiunobbs-v4.0.7/route/user.php#L7-L404)
```php
$action = param(1);
if(empty($action)) {
// 用户主页
$_uid = param(1, 0);
$_user = user_read($_uid);
include _include(APP_PATH.'view/htm/user.htm');
} elseif($action == 'thread') {
// 用户主题列表
$_uid = param(2, 0);
$page = param(3, 1);
$threadlist = mythread_find_by_uid($_uid, $page, $pagesize);
include _include(APP_PATH.'view/htm/user_thread.htm');
} elseif($action == 'login') {
// 用户登录
if($method == 'GET') {
include _include(APP_PATH.'view/htm/user_login.htm');
} else if($method == 'POST') {
// 处理登录逻辑
$email = param('email');
$password = param('password');
$_user = user_read_by_email($email);
// ... 验证逻辑
message(0, lang('user_login_successfully'));
}
} elseif($action == 'create') {
// 用户注册
// ... 注册逻辑
} elseif($action == 'logout') {
// 用户登出
// ... 登出逻辑
} elseif($action == 'resetpw') {
// 重置密码
// ... 重置密码逻辑
}
```
**子路由示例**:
```
user-123.htm → action = '123' → 用户主页
user-thread-123-1.htm → action = 'thread' → 用户主题列表
user-login.htm → action = 'login' → 登录页面
user-create.htm → action = 'create' → 注册页面
user-logout.htm → action = 'logout' → 登出操作
```
### 10. 前置判断
在执行业务逻辑前,通常会进行权限和状态检查。
**常见前置判断**:
#### 10.1 登录检查
```php
// route/thread.php - 发表主题
if($action == 'create') {
user_login_check(); // 检查用户是否登录
// ... 发表逻辑
}
```
#### 10.2 权限检查
```php
// route/thread.php - 帖子详情
$thread = thread_read($tid);
$fid = $thread['fid'];
forum_access_user($fid, $gid, 'allowread') OR message(-1, lang('user_group_insufficient_privilege'));
```
#### 10.3 参数验证
```php
// route/thread.php - 发表主题
$subject = param('subject');
empty($subject) AND message('subject', lang('please_input_subject'));
xn_strlen($subject) > 128 AND message('subject', lang('subject_length_over_limit', array('maxlength'=>128)));
```
#### 10.4 数据存在性检查
```php
// route/thread.php - 帖子详情
$thread = thread_read($tid);
empty($thread) AND message(-1, lang('thread_not_exists'));
$forum = forum_read($fid);
empty($forum) AND message(3, lang('forum_not_exists'));
```
### 11. Model 层数据处理
Route 层调用 Model 层函数进行数据操作。
**示例:获取首页主题列表**
**代码位置**:[route/index.php:11-28](file:///m:/laragon/www/xiunobbs-v4.0.7/route/index.php#L11-L28)
```php
// 获取有权限查看的版块 ID 列表
$fids = arrlist_values($forumlist_show, 'fid');
// 获取主题总数
$threads = arrlist_sum($forumlist_show, 'threads');
// 生成分页
$pagination = pagination(url("$route-{page}"), $threads, $page, $pagesize);
// 调用 Model 层获取主题列表
$threadlist = thread_find_by_fids($fids, $page, $pagesize, $order, $threads);
// 查找置顶帖
if($order == $conf['order_default'] && $page == 1) {
$toplist3 = thread_top_find(0);
$threadlist = $toplist3 + $threadlist;
}
// 过滤没有权限访问的主题
thread_list_access_filter($threadlist, $gid);
```
**示例:获取帖子详情**
**代码位置**:[route/thread.php:92-113](file:///m:/laragon/www/xiunobbs-v4.0.7/route/thread.php#L92-L113)
```php
// 读取主题信息
$thread = thread_read($tid);
// 读取版块信息
$fid = $thread['fid'];
$forum = forum_read($fid);
// 读取帖子列表
$postlist = post_find_by_tid($tid, $page, $pagesize);
// 读取首帖
if($page == 1) {
$first = $postlist[$thread['firstpid']];
unset($postlist[$thread['firstpid']]);
// 增加浏览次数
thread_inc_views($tid);
} else {
$first = post_read($thread['firstpid']);
}
```
### 12. 数据库操作
Model 层调用 Xiuno PHP 核心的数据库函数执行 SQL 查询。
**Model 层函数示例**:
```php
// model/thread.func.php
function thread_read($tid) {
global $db;
// 从缓存读取
$thread = cache_get("thread_$tid");
if($thread !== NULL) {
return $thread;
}
// 从数据库读取
$thread = db_read('thread', array('tid'=>$tid));
// 写入缓存
cache_set("thread_$tid", $thread);
return $thread;
}
function thread_find_by_fid($fid, $page, $pagesize, $orderby) {
global $db;
$offset = ($page - 1) * $pagesize;
$threadlist = db_find('thread',
array('fid'=>$fid),
array($orderby=>-1),
$page,
$pagesize
);
return $threadlist;
}
```
**核心数据库函数**:
```php
// xiunophp 核心函数
db_connect() // 连接数据库
db_query($sql) // 执行 SQL
db_read($table, $cond) // 读取单条记录
db_find($table, $cond) // 读取多条记录
db_create($table, $data) // 插入记录
db_update($table, $cond, $data) // 更新记录
db_delete($table, $cond) // 删除记录
```
### 13. 数据格式化处理
Model 层返回的数据通常需要进一步格式化。
**常见格式化函数**:
```php
// model/thread.func.php
function thread_format(&$thread) {
global $conf;
// 格式化时间
$thread['create_date_fmt'] = date('Y-m-d H:i', $thread['create_date']);
// 格式化用户名
if(empty($thread['username'])) {
$thread['username'] = lang('guest');
}
// 格式化主题内容
$thread['subject'] = htmlspecialchars($thread['subject']);
// 添加 URL
$thread['url'] = url("thread-{$thread['tid']}");
return $thread;
}
// model/user.func.php
function user_format(&$user) {
// 格式化头像 URL
$user['avatar_url'] = user_avatar_url($user['uid']);
// 格式化用户组名称
$user['groupname'] = group_name($user['gid']);
return $user;
}
```
**批量格式化**:
```php
// model/thread.func.php
function thread_list_format(&$threadlist) {
foreach($threadlist as &$thread) {
thread_format($thread);
}
return $threadlist;
}
```
### 14. 视图渲染
数据处理完成后,调用视图文件进行渲染。
**代码位置**:[route/index.php:47](file:///m:/laragon/www/xiunobbs-v4.0.7/route/index.php#L47)
```php
include _include(APP_PATH.'view/htm/index.htm');
```
**视图文件结构**:
```
view/htm/
├── index.htm // 首页
├── thread.htm // 帖子详情页
├── forum.htm // 版块列表页
├── user.htm // 用户主页
├── user_login.htm // 登录页
├── user_create.htm // 注册页
├── post.htm // 发帖页
└── ... 其他视图文件
```
**视图文件示例**:
```html
```
**_include 函数的作用**:
```php
function _include($filepath) {
// 1. 检查是否有插件覆盖该视图文件
// 2. 如果有,返回插件视图文件路径
// 3. 否则,返回原视图文件路径
// 这样插件可以覆盖任何视图文件
}
```
### 15. 输出 HTML
视图文件中的 HTML 和 PHP 代码混合执行,最终输出完整的 HTML 页面给用户浏览器。
**输出流程**:
1. PHP 引擎解析视图文件
2. 执行视图中的 PHP 代码
3. 生成纯 HTML 内容
4. 发送到用户浏览器
5. 浏览器渲染页面
**输出缓冲**:
```php
// index.php 开启输出缓冲
// ob_start('ob_gzhandler'); // 可选:启用 gzip 压缩
```
---
## 完整示例分析
### 示例 1:访问首页
**URL**:`http://example.com/`
**执行流程**:
1. **初始化阶段**
- `index.php` 加载配置文件
- 连接数据库
- 加载插件机制
- 加载 Model 层
- 进入 `index.inc.php`
2. **路由分发**
- `$route = param(0, 'index')` → 获取路由为 `index`
- `switch($route)` 匹配到 `case 'index'`
- 加载 `route/index.php`
3. **业务逻辑**
- 获取有权限的版块列表:`$fids = arrlist_values($forumlist_show, 'fid')`
- 获取主题总数:`$threads = arrlist_sum($forumlist_show, 'threads')`
- 生成分页:`$pagination = pagination(...)`
- 获取主题列表:`$threadlist = thread_find_by_fids(...)`
- 获取置顶帖:`$toplist3 = thread_top_find(0)`
- 过滤权限:`thread_list_access_filter($threadlist, $gid)`
4. **视图渲染**
- 加载视图文件:`include _include(APP_PATH.'view/htm/index.htm')`
- 视图文件遍历 `$threadlist` 输出主题列表
- 输出分页导航
5. **输出 HTML**
- 生成完整 HTML 页面
- 发送到浏览器
### 示例 2:查看帖子详情
**URL**:`http://example.com/thread-123.htm`
**执行流程**:
1. **初始化阶段**(同上)
2. **路由分发**
- `$route = param(0, 'index')` → 获取路由为 `thread`
- 加载 `route/thread.php`
3. **子路由判断**
- `$action = param(1)` → 获取 `123`(帖子 ID)
- `$action` 不是 `create`,进入帖子详情逻辑
4. **业务逻辑**
- 读取主题:`$thread = thread_read($tid)`
- 读取版块:`$forum = forum_read($fid)`
- 权限检查:`forum_access_user($fid, $gid, 'allowread')`
- 读取帖子列表:`$postlist = post_find_by_tid($tid, $page, $pagesize)`
- 读取首帖:`$first = $postlist[$thread['firstpid']]`
- 增加浏览数:`thread_inc_views($tid)`
- 生成分页:`$pagination = pagination(...)`
5. **视图渲染**
- 加载视图文件:`include _include(APP_PATH.'view/htm/thread.htm')`
- 输出主题信息
- 输出首帖内容
- 输出回复列表
- 输出分页导航
### 示例 3:用户登录
**URL**:
- GET 请求:`http://example.com/user-login.htm`
- POST 请求:`http://example.com/user-login.htm`(提交表单)
**执行流程**:
1. **初始化阶段**(同上)
2. **路由分发**
- `$route = param(0, 'index')` → 获取路由为 `user`
- 加载 `route/user.php`
3. **子路由判断**
- `$action = param(1)` → 获取 `login`
- 进入登录逻辑
4. **GET 请求(显示登录表单)**
- 加载视图文件:`include _include(APP_PATH.'view/htm/user_login.htm')`
- 输出登录表单
5. **POST 请求(处理登录)**
- 获取参数:`$email = param('email')`, `$password = param('password')`
- 验证邮箱格式:`is_email($email, $err)`
- 查找用户:`$_user = user_read_by_email($email)`
- 验证密码:`md5($password.$_user['salt']) == $_user['password']`
- 更新登录信息:`user_update($_user['uid'], array('login_ip'=>$longip, 'login_date'=>$time, 'logins+'=>1))`
- 设置会话:`$_SESSION['uid'] = $uid`
- 设置 Token:`user_token_set($_user['uid'])`
- 返回成功消息:`message(0, lang('user_login_successfully'))`
---
## 关键函数说明
### param() - 获取请求参数
```php
/**
* 获取 URL 参数
* @param int $n 参数位置(0, 1, 2, ...)
* @param mixed $default 默认值
* @return mixed 参数值
*/
function param($n, $default = NULL) {
// 从 URL 解析参数
// 例如:thread-123-1.htm
// param(0) = 'thread'
// param(1) = '123'
// param(2) = '1'
}
```
### _include() - 插件文件包含
```php
/**
* 包含文件(支持插件覆盖)
* @param string $filepath 原始文件路径
* @return string 实际文件路径(可能是插件覆盖后的路径)
*/
function _include($filepath) {
// 检查插件是否覆盖该文件
// 如果有,返回插件文件路径
// 否则,返回原文件路径
}
```
### message() - 输出消息
```php
/**
* 输出消息(通常用于 AJAX 响应)
* @param int $code 状态码(0=成功,负数=错误)
* @param string $message 消息内容
* @param array $extra 额外数据
*/
function message($code, $message = '', $extra = array()) {
// 输出 JSON 格式消息
// {code: 0, message: '操作成功', extra: {...}}
}
```
### url() - 生成 URL
```php
/**
* 生成 URL
* @param string $route 路由字符串
* @param array $extra 额外参数
* @return string URL
*/
function url($route, $extra = array()) {
// 例如:url('thread-123') → 'thread-123.htm'
// 例如:url('forum-5-1', array('orderby'=>'tid')) → 'forum-5-1.htm?orderby=tid'
}
```
### pagination() - 生成分页
```php
/**
* 生成分页导航
* @param string $url URL 模板
* @param int $total 总记录数
* @param int $page 当前页码
* @param int $pagesize 每页记录数
* @return string 分页 HTML
*/
function pagination($url, $total, $page, $pagesize) {
// 生成分页导航 HTML
// 例如:
}
```
### cache_get() / cache_set() - 缓存操作
```php
/**
* 获取缓存
* @param string $key 缓存键名
* @return mixed 缓存值(不存在返回 NULL)
*/
function cache_get($key) {
// 从缓存系统读取数据
// 支持文件缓存、Memcache、Redis 等
}
/**
* 设置缓存
* @param string $key 缓存键名
* @param mixed $value 缓存值
* @param int $life 缓存生命周期(秒)
*/
function cache_set($key, $value, $life = 0) {
// 写入缓存系统
}
```
---
## 总结
Xiuno BBS 4.0 的请求处理流程遵循经典的 MVC 架构模式:
1. **单一入口**:所有请求通过 `index.php` 统一处理
2. **清晰分层**:Model-View-Controller 三层分离
3. **插件机制**:通过 `_include()` 函数实现文件覆盖和 Hook 机制
4. **缓存优化**:Model 层内置缓存机制,减少数据库查询
5. **权限控制**:统一的权限检查函数,确保安全性
理解这个流程对于开发 Xiuno BBS 插件、调试问题、性能优化都至关重要。
---
## 相关文档
- [Xiuno BBS 插件机制工作原理](file:///m:/laragon/www/xiunobbs-v4.0.7/开发资料/程序本体/Xiuno%20BBS%20插件机制工作原理.mmd)
- [Xiuno BBS 数据库表结构详解](file:///m:/laragon/www/xiunobbs-v4.0.7/开发资料/程序本体/Xiuno%20BBS%20数据库表结构详解.md)
- [Xiuno BBS 缓存系统详解](file:///m:/laragon/www/xiunobbs-v4.0.7/开发资料/程序本体/Xiuno%20BBS%20缓存系统详解.md)
- [Xiuno PHP 4.0 开发手册](file:///m:/laragon/www/xiunobbs-v4.0.7/开发资料/程序本体/Xiuno%20PHP%204.0开发手册%20清扫版本.md)