本文目录导读:

我来为您详细介绍如何用PHP实现一个完整的FAQ系统。
系统设计
数据库设计
首先创建数据库表:
CREATE TABLE `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `sort_order` int(11) DEFAULT 0, `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `faqs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL, `question` text NOT NULL, `answer` text NOT NULL, `status` tinyint(1) DEFAULT 1, `sort_order` int(11) DEFAULT 0, `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
项目结构
faq-system/
├── config/
│ └── database.php
├── includes/
│ ├── header.php
│ └── footer.php
├── admin/
│ ├── index.php
│ ├── categories.php
│ ├── faqs.php
│ └── login.php
├── api/
│ ├── search.php
│ └── categories.php
├── assets/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── main.js
├── index.php
└── search.php
核心代码实现
数据库连接配置 (config/database.php)
<?php
class Database {
private $host = "localhost";
private $db_name = "faq_system";
private $username = "root";
private $password = "";
public $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host=" . $this->host . ";dbname=" . $this->db_name,
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $exception) {
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
FAQ主页面 (index.php)
<?php
require_once 'config/database.php';
$database = new Database();
$db = $database->getConnection();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">FAQ 常见问题系统</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<?php include 'includes/header.php'; ?>
<div class="container">
<!-- 搜索功能 -->
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索常见问题..." onkeyup="searchFAQ()">
</div>
<!-- FAQ列表 -->
<div class="faq-list">
<?php
// 获取所有分类
$query = "SELECT * FROM categories ORDER BY sort_order ASC";
$stmt = $db->prepare($query);
$stmt->execute();
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($categories as $category) {
echo "<div class='category'>";
echo "<h2>" . htmlspecialchars($category['name']) . "</h2>";
// 获取该分类下的FAQ
$faqQuery = "SELECT * FROM faqs WHERE category_id = :category_id AND status = 1 ORDER BY sort_order ASC";
$faqStmt = $db->prepare($faqQuery);
$faqStmt->bindParam(':category_id', $category['id']);
$faqStmt->execute();
$faqs = $faqStmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($faqs as $faq) {
echo "<div class='faq-item'>";
echo "<div class='faq-question' onclick='toggleAnswer(this)'>";
echo "<span>" . htmlspecialchars($faq['question']) . "</span>";
echo "<span class='arrow'>▶</span>";
echo "</div>";
echo "<div class='faq-answer' style='display:none'>";
echo nl2br(htmlspecialchars($faq['answer']));
echo "</div>";
echo "</div>";
}
echo "</div>";
}
?>
</div>
</div>
<?php include 'includes/footer.php'; ?>
<script src="assets/js/main.js"></script>
</body>
</html>
AJAX搜索功能 (api/search.php)
<?php
require_once '../config/database.php';
$database = new Database();
$db = $database->getConnection();
$searchTerm = isset($_GET['q']) ? $_GET['q'] : '';
if ($searchTerm) {
// 搜索相关问题
$query = "SELECT f.*, c.name as category_name
FROM faqs f
LEFT JOIN categories c ON f.category_id = c.id
WHERE (f.question LIKE :search1 OR f.answer LIKE :search2)
AND f.status = 1
ORDER BY f.sort_order ASC";
$stmt = $db->prepare($query);
$searchTerm = "%{$searchTerm}%";
$stmt->bindParam(':search1', $searchTerm);
$stmt->bindParam(':search2', $searchTerm);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
header('Content-Type: application/json');
echo json_encode($results);
}
?>
后台管理 (admin/faqs.php)
<?php
session_start();
require_once '../config/database.php';
// 简单登录检查
if (!isset($_SESSION['admin_logged_in'])) {
header('Location: login.php');
exit();
}
$database = new Database();
$db = $database->getConnection();
// 添加FAQ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add') {
$query = "INSERT INTO faqs (category_id, question, answer, status) VALUES (:category_id, :question, :answer, :status)";
$stmt = $db->prepare($query);
$stmt->bindParam(':category_id', $_POST['category_id']);
$stmt->bindParam(':question', $_POST['question']);
$stmt->bindParam(':answer', $_POST['answer']);
$stmt->bindParam(':status', $_POST['status']);
$stmt->execute();
}
// 更新FAQ
if ($_POST['action'] === 'update') {
$query = "UPDATE faqs SET category_id = :category_id, question = :question, answer = :answer, status = :status WHERE id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $_POST['id']);
$stmt->bindParam(':category_id', $_POST['category_id']);
$stmt->bindParam(':question', $_POST['question']);
$stmt->bindParam(':answer', $_POST['answer']);
$stmt->bindParam(':status', $_POST['status']);
$stmt->execute();
}
// 删除FAQ
if ($_POST['action'] === 'delete') {
$query = "DELETE FROM faqs WHERE id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $_POST['id']);
$stmt->execute();
}
}
// 获取所有分类
$categoryQuery = "SELECT * FROM categories ORDER BY sort_order ASC";
$categoryStmt = $db->prepare($categoryQuery);
$categoryStmt->execute();
$categories = $categoryStmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">FAQ管理后台</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="admin-container">
<h1>FAQ管理后台</h1>
<!-- 添加FAQ表单 -->
<div class="add-faq-form">
<h2>添加新FAQ</h2>
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="form-group">
<label>分类:</label>
<select name="category_id" required>
<?php foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>">
<?php echo htmlspecialchars($category['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>问题:</label>
<input type="text" name="question" required>
</div>
<div class="form-group">
<label>答案:</label>
<textarea name="answer" rows="5" required></textarea>
</div>
<div class="form-group">
<label>状态:</label>
<select name="status">
<option value="1">启用</option>
<option value="0">禁用</option>
</select>
</div>
<button type="submit">添加FAQ</button>
</form>
</div>
<!-- FAQ列表 -->
<div class="faq-table">
<h2>FAQ列表</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>分类</th>
<th>问题</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php
$query = "SELECT f.*, c.name as category_name
FROM faqs f
LEFT JOIN categories c ON f.category_id = c.id
ORDER BY f.sort_order ASC";
$stmt = $db->prepare($query);
$stmt->execute();
$faqs = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($faqs as $faq): ?>
<tr>
<td><?php echo $faq['id']; ?></td>
<td><?php echo htmlspecialchars($faq['category_name']); ?></td>
<td><?php echo htmlspecialchars($faq['question']); ?></td>
<td><?php echo $faq['status'] ? '启用' : '禁用'; ?></td>
<td>
<button onclick="editFAQ(<?php echo $faq['id']; ?>)">编辑</button>
<form method="POST" style="display:inline">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?php echo $faq['id']; ?>">
<button type="submit" onclick="return confirm('确定删除?')">删除</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</body>
</html>
JavaScript交互 (assets/js/main.js)
// 切换答案显示
function toggleAnswer(element) {
const answer = element.nextElementSibling;
const arrow = element.querySelector('.arrow');
if (answer.style.display === 'none') {
answer.style.display = 'block';
arrow.innerHTML = '▼';
} else {
answer.style.display = 'none';
arrow.innerHTML = '▶';
}
}
// 搜索功能
function searchFAQ() {
const searchTerm = document.getElementById('searchInput').value;
if (searchTerm.length < 2) {
// 如果搜索词太短,显示所有内容
document.querySelectorAll('.faq-item').forEach(item => {
item.style.display = 'block';
});
return;
}
fetch(`api/search.php?q=${encodeURIComponent(searchTerm)}`)
.then(response => response.json())
.then(data => {
// 隐藏所有FAQ项
document.querySelectorAll('.faq-item').forEach(item => {
item.style.display = 'none';
});
// 显示匹配的结果
data.forEach(faq => {
const items = document.querySelectorAll('.faq-item');
// 这里需要根据返回的数据显示匹配的FAQ
});
})
.catch(error => console.error('Error:', error));
}
// 编辑FAQ功能
function editFAQ(id) {
// 获取FAQ数据并显示编辑表单
fetch(`api/get_faq.php?id=${id}`)
.then(response => response.json())
.then(data => {
// 填充编辑表单
document.getElementById('editForm').style.display = 'block';
document.getElementById('edit_id').value = data.id;
document.getElementById('edit_question').value = data.question;
document.getElementById('edit_answer').value = data.answer;
});
}
CSS样式 (assets/css/style.css)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.search-box {
margin: 20px 0;
text-align: center;
}
.search-box input {
width: 60%;
padding: 12px 20px;
border: 2px solid #ddd;
border-radius: 25px;
font-size: 16px;
transition: border-color 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #007bff;
}
.category {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.category h2 {
color: #007bff;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
margin-bottom: 15px;
}
.faq-item {
border: 1px solid #eee;
border-radius: 5px;
margin-bottom: 10px;
overflow: hidden;
}
.faq-question {
background: #f8f9fa;
padding: 15px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.3s;
}
.faq-question:hover {
background: #e9ecef;
}
.faq-question .arrow {
transition: transform 0.3s;
font-size: 12px;
}
.faq-answer {
padding: 15px;
line-height: 1.8;
background: white;
border-top: 1px solid #eee;
}
.admin-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
th {
background: #f8f9fa;
}
tr:hover {
background: #f5f5f5;
}
功能特点
- 分类管理:FAQ按分类组织,便于用户查找
- 搜索功能:实时搜索,用户快速找到答案
- 折叠展开:点击问题展开/关闭答案
- 后台管理:完整的CRUD操作
- 响应式设计:适配移动端和桌面端
- 状态管理:启用/禁用FAQ
安全建议
<?php
// SQL注入防护 - 使用预处理语句
$stmt = $db->prepare("SELECT * FROM faqs WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
// XSS防护 - 输出过滤
echo htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
// CSRF防护
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
?>
性能优化建议
- 缓存实现:使用memcached或Redis缓存热门FAQ
- 数据库索引:为搜索字段添加索引
- 懒加载:按需加载FAQ内容
- CDN加速:使用CDN托管静态资源
部署步骤
- 创建数据库并导入SQL文件
- 修改config/database.php中的数据库配置
- 设置目录权限(确保uploads目录可写)
- 配置Web服务器(Apache/Nginx)
- 访问后台地址/admin/login.php进行管理
这个FAQ系统提供了完整的解决方案,您可以根据实际需求进行扩展和修改。