本文目录导读:

我来帮你了解如何使用 PHP 构建一个 Wiki 系统,这里提供一个从简单到完整方案的全景介绍:
使用现成的 Wiki 系统
MediaWiki(最流行)
# 需要环境:PHP 7.4+, MySQL, Composer composer create-project mediawiki/core my-wiki
DokuWiki(轻量级)
# 只需要 PHP,不需要数据库 wget https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz tar -xzf dokuwiki-stable.tgz
Tiki Wiki(功能丰富)
composer create-project tikiwiki/tiki my-tiki
从零开发简单 Wiki
基础架构设计
// config.php
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'wiki_db');
define('DB_USER', 'root');
define('DB_PASS', '');
define('BASE_URL', 'http://localhost/wiki');
define('UPLOAD_DIR', __DIR__ . '/uploads/');
// 数据库连接
function db_connect() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
return $conn;
}
?>
创建数据库表
// install.php
<?php
$conn = db_connect();
$sql = "
CREATE TABLE IF NOT EXISTS pages (
id INT AUTO_INCREMENT PRIMARY KEY,VARCHAR(255) NOT NULL UNIQUE,
content MEDIUMTEXT,
slug VARCHAR(255) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS revisions (
id INT AUTO_INCREMENT PRIMARY KEY,
page_id INT,
content MEDIUMTEXT,
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password_hash VARCHAR(255),
email VARCHAR(100),
role ENUM('admin', 'editor', 'viewer') DEFAULT 'viewer',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL
);
CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) UNIQUE,
description TEXT,
parent_id INT NULL
);
CREATE TABLE IF NOT EXISTS pages_categories (
page_id INT,
category_id INT,
PRIMARY KEY (page_id, category_id)
);
CREATE TABLE IF NOT EXISTS tags (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) UNIQUE
);
CREATE TABLE IF NOT EXISTS pages_tags (
page_id INT,
tag_id INT,
PRIMARY KEY (page_id, tag_id)
);
CREATE TABLE IF NOT EXISTS search_terms (
id INT AUTO_INCREMENT PRIMARY KEY,
term VARCHAR(255) UNIQUE,
page_id INT,
weight FLOAT DEFAULT 1.0
);
";
$conn->query($sql);
?>
页面管理类
// Page.php
<?php
class Page {
private $conn;
private $table = 'pages';
public function __construct() {
$this->conn = db_connect();
}
// 创建页面
public function create($title, $content, $category_ids = [], $tags = []) {
$slug = $this->createSlug($title);
$stmt = $this->conn->prepare("INSERT INTO $this->table (title, content, slug) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $title, $content, $slug);
if ($stmt->execute()) {
$page_id = $stmt->insert_id;
$this->addCategories($page_id, $category_ids);
$this->addTags($page_id, $tags);
$this->updateSearchIndex($page_id, $content);
return $page_id;
}
return false;
}
// 获取页面
public function getPage($id = null, $slug = null) {
if ($id) {
$sql = "SELECT * FROM $this->table WHERE id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $id);
} else if ($slug) {
$sql = "SELECT * FROM $this->table WHERE slug = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $slug);
}
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();
}
// 更新页面
public function update($id, $content, $category_ids = [], $tags = []) {
$sql = "UPDATE $this->table SET content = ? WHERE id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("si", $content, $id);
if ($stmt->execute()) {
// 保存修订历史
$this->saveRevision($id, $content);
$this->updateCategories($id, $category_ids);
$this->updateTags($id, $tags);
$this->updateSearchIndex($id, $content);
return true;
}
return false;
}
// 删除页面
public function delete($id) {
$this->conn->begin_transaction();
try {
// 删除相关数据
$this->conn->query("DELETE FROM pages_categories WHERE page_id = $id");
$this->conn->query("DELETE FROM pages_tags WHERE page_id = $id");
$this->conn->query("DELETE FROM revisions WHERE page_id = $id");
$this->conn->query("DELETE FROM search_terms WHERE page_id = $id");
// 删除页面
$sql = "DELETE FROM $this->table WHERE id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$this->conn->commit();
return true;
} catch (Exception $e) {
$this->conn->rollback();
return false;
}
}
// 搜索页面
public function search($query) {
$sql = "SELECT p.*, MATCH(p.content, p.title) AS relevance
FROM $this->table p
WHERE MATCH(p.content, p.title) AGAINST (?)
ORDER BY relevance DESC LIMIT 10";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $query);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
// 创建 URL 别名
private function createSlug($title) {
$slug = strtolower($title);
$slug = preg_replace('/[^a-z0-9-]/', '-', $slug);
$slug = preg_replace('/-+/', '-', $slug);
$slug = trim($slug, '-');
return $slug;
}
// 保存修订
private function saveRevision($page_id, $content) {
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 1;
$stmt = $this->conn->prepare("INSERT INTO revisions (page_id, content, user_id) VALUES (?, ?, ?)");
$stmt->bind_param("isi", $page_id, $content, $user_id);
$stmt->execute();
}
// 更新索引
private function updateSearchIndex($page_id, $content) {
// 清除旧索引
$this->conn->query("DELETE FROM search_terms WHERE page_id = $page_id");
// 提取关键词(简化示例)
$words = str_word_count(strip_tags($content), 1);
$stop_words = ['the', 'a', 'an', 'and', 'or', 'but'];
$word_count = array_count_values($words);
foreach ($word_count as $word => $count) {
if (!in_array($word, $stop_words) && strlen($word) > 3) {
$weight = $count / max(1, count($words));
$stmt = $this->conn->prepare("INSERT IGNORE INTO search_terms (term, page_id, weight) VALUES (?, ?, ?)");
$stmt->bind_param("sid", $word, $page_id, $weight);
$stmt->execute();
}
}
}
// 关联分类
private function addCategories($page_id, $category_ids) {
if (empty($category_ids)) return;
foreach ($category_ids as $cat_id) {
$stmt = $this->conn->prepare("INSERT IGNORE INTO pages_categories (page_id, category_id) VALUES (?, ?)");
$stmt->bind_param("ii", $page_id, $cat_id);
$stmt->execute();
}
}
// 添加标签
private function addTags($page_id, $tags) {
if (empty($tags)) return;
foreach ($tags as $tag) {
// 获取或创建标签
$stmt = $this->conn->prepare("INSERT IGNORE INTO tags (name) VALUES (?)");
$stmt->bind_param("s", $tag);
$stmt->execute();
$tag_id = $this->conn->insert_id ?: $this->getTagId($tag);
$stmt = $this->conn->prepare("INSERT IGNORE INTO pages_tags (page_id, tag_id) VALUES (?, ?)");
$stmt->bind_param("ii", $page_id, $tag_id);
$stmt->execute();
}
}
private function getTagId($name) {
$sql = "SELECT id FROM tags WHERE name = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $name);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['id'] ?? 0;
}
}
?>
前端控制器
// router.php
<?php
// 简单的路由器
class Router {
private $routes = [];
public function get($path, $callback) {
$this->routes['GET'][$path] = $callback;
}
public function post($path, $callback) {
$this->routes['POST'][$path] = $callback;
}
public function resolve() {
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
foreach ($this->routes[$method] ?? [] as $route => $callback) {
if ($route === $path) {
return call_user_func($callback);
}
}
http_response_code(404);
echo "404 Not Found";
}
}
$router = new Router();
// 页面路由
$router->get('/wiki', function() {
$page = new Page();
$pages = $page->getAll();
include 'templates/home.php';
});
$router->get('/wiki/page/{id}', function($id) {
$page = new Page();
$data = $page->getPage($id);
include 'templates/page-view.php';
});
// 创建页面
$router->get('/wiki/create', function() {
include 'templates/page-create.php';
});
$router->post('/wiki/create', function() {
$page = new Page();
$page->create($_POST['title'], $_POST['content']);
header("Location: /wiki/page/" . $page->getLastId());
});
// 编辑页面
$router->get('/wiki/edit/{id}', function($id) {
$page = new Page();
$data = $page->getPage($id);
include 'templates/page-edit.php';
});
$router->post('/wiki/edit/{id}', function($id) {
$page = new Page();
$page->update($id, $_POST['content']);
header("Location: /wiki/page/$id");
});
// 搜索
$router->get('/wiki/search', function() {
$page = new Page();
$results = $page->search($_GET['q'] ?? '');
include 'templates/search-results.php';
});
$router->resolve();
?>
使用框架构建
使用 Laravel(推荐)
composer create-project laravel/laravel wiki-app
模型
// app/Models/Page.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Page extends Model {
protected $fillable = ['title', 'content', 'slug', 'category_id'];
public function categories() {
return $this->belongsToMany(Category::class);
}
public function tags() {
return $this->belongsToMany(Tag::class);
}
public function revisions() {
return $this->hasMany(Revision::class);
}
public function user() {
return $this->belongsTo(User::class);
}
public function searchableAs() {
return 'wiki_pages';
}
}
?>
控制器
// app/Http/Controllers/WikiController.php
<?php
namespace App\Http\Controllers;
use App\Models\Page;
use Illuminate\Http\Request;
class WikiController extends Controller {
public function index() {
$pages = Page::latest()->paginate(10);
return view('wiki.index', compact('pages'));
}
public function show($slug) {
$page = Page::where('slug', $slug)->firstOrFail();
return view('wiki.show', compact('page'));
}
public function create() {
return view('wiki.create');
}
public function store(Request $request) {
$validated = $request->validate([
'title' => 'required|unique:pages',
'content' => 'required',
]);
$page = Page::create($validated);
return redirect()->route('wiki.show', $page->slug);
}
public function edit($slug) {
$page = Page::where('slug', $slug)->firstOrFail();
return view('wiki.edit', compact('page'));
}
public function update(Request $request, $slug) {
$page = Page::where('slug', $slug)->firstOrFail();
$validated = $request->validate([
'title' => 'required|unique:pages,title,'.$page->id,
'content' => 'required',
]);
$page->update($validated);
return redirect()->route('wiki.show', $page->slug);
}
public function search(Request $request) {
$query = $request->input('q');
$pages = Page::where('title', 'LIKE', "%{$query}%")
->orWhere('content', 'LIKE', "%{$query}%")
->get();
return view('wiki.search', compact('pages', 'query'));
}
}
?>
推荐学习资源
-
官方文档:
- PHP 官方文档:https://www.php.net/manual/
- Laravel 文档:https://laravel.com/docs
-
学习路径:
- 基础:变量、数组、函数、OOP
- 进阶:MVC 架构、路由、中间件
- 实践:CRUD 应用、用户认证
-
工具推荐:
- IDE:PhpStorm、VS Code + PHP Intelephense
- 调试:Xdebug
- 包管理:Composer
-
完整 Wiki 系统推荐:
- MediaWiki(资料最多)
- DokuWiki(轻量简单)
- BookStack(界面美观)
建议先使用现成系统了解 Wiki 功能,再根据需求决定是否自己开发,如果自己开发,推荐使用 Laravel 框架,开发效率更高,需要更详细的代码示例吗?