单页论坛系统实现方案

50 阅读4分钟

系统架构

前端部分

  • HTML5单页应用
  • CSS3样式
  • JavaScript (使用jQuery简化DOM操作)
  • AJAX与后端交互

后端部分

  • PHP处理业务逻辑
  • JSON存储数据
  • 采集功能使用PHP cURL和正则表达式

伪静态支持

  • IIS伪静态规则
  • 宝塔面板伪静态规则

实现步骤

1. 文件结构

/forum/
├── index.php            # 主入口文件
├── admin/               # 后台管理
│   ├── index.php        # 后台首页
│   ├── settings.php     # 采集设置
│   └── collect.php      # 执行采集
├── data/                # 数据存储
│   ├── posts.json       # 帖子数据
│   └── settings.json    # 采集配置
├── assets/              # 静态资源
│   ├── css/
│   │   └── style.css
│   └── js/
│       └── main.js
└── .htaccess            # Apache伪静态规则

2. 数据库结构 (JSON格式)

data/posts.json:

{
    "posts": [
        {
            "id": 1,
            "title": "示例帖子",
            "content": "这是帖子内容",
            "author": "admin",
            "date": "2023-05-01 10:00:00",
            "views": 100
        }
    ],
    "total": 1,
    "per_page": 10
}

data/settings.json (采集配置):

{
    "target_url": "https://example.com/forum",
    "list_rule": {
        "pattern": "<div class=\"item\">.*?<a href=\"(.*?)\".*?>(.*?)</a>.*?</div>",
        "modifiers": "s"
    },
    "content_rule": {
        "pattern": "<div class=\"content\">(.*?)</div>",
        "modifiers": "s"
    },
    "interval": 3600,
    "last_collect": "2023-05-01 10:00:00"
}

3. 前端实现 (index.php)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单页论坛</title>
    <link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
    <header>
        <h1>单页论坛</h1>
        <nav>
            <a href="/">首页</a>
            <a href="/admin" target="_blank">管理</a>
        </nav>
    </header>
    
    <div id="forum-container">
        <div id="posts-list"></div>
        <div id="pagination"></div>
    </div>
    
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="assets/js/main.js"></script>
</body>
</html>

4. JavaScript (main.js)

$(document).ready(function() {
    // 加载帖子列表
    loadPosts(1);
    
    // 翻页事件
    $(document).on('click', '.page-link', function(e) {
        e.preventDefault();
        var page = $(this).data('page');
        loadPosts(page);
    });
});

function loadPosts(page) {
    $.ajax({
        url: '/api/posts?page=' + page,
        type: 'GET',
        dataType: 'json',
        success: function(response) {
            renderPosts(response.posts);
            renderPagination(page, Math.ceil(response.total / response.per_page));
        }
    });
}

function renderPosts(posts) {
    var html = '';
    posts.forEach(function(post) {
        html += `
            <div class="post">
                <h3><a href="/post/${post.id}" class="post-link">${post.title}</a></h3>
                <p>${post.content.substring(0, 100)}...</p>
                <div class="meta">
                    <span>作者: ${post.author}</span>
                    <span>日期: ${post.date}</span>
                    <span>浏览: ${post.views}</span>
                </div>
            </div>
        `;
    });
    $('#posts-list').html(html);
}

function renderPagination(currentPage, totalPages) {
    var html = '<div class="pagination">';
    
    if (currentPage > 1) {
        html += `<a href="#" class="page-link" data-page="${currentPage - 1}">上一页</a>`;
    }
    
    for (var i = 1; i <= totalPages; i++) {
        if (i === currentPage) {
            html += `<span class="current">${i}</span>`;
        } else {
            html += `<a href="#" class="page-link" data-page="${i}">${i}</a>`;
        }
    }
    
    if (currentPage < totalPages) {
        html += `<a href="#" class="page-link" data-page="${currentPage + 1}">下一页</a>`;
    }
    
    html += '</div>';
    $('#pagination').html(html);
}

5. PHP后端处理 (index.php)

<?php
// 开启伪静态时,实际请求会由index.php处理
define('DATA_DIR', __DIR__ . '/data');

// 路由处理
$request = $_SERVER['REQUEST_URI'];
$request = str_replace('/forum', '', $request); // 如果放在子目录

if (strpos($request, '/api/') === 0) {
    // API请求
    header('Content-Type: application/json');
    
    if (strpos($request, '/api/posts') === 0) {
        $page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
        echo getPosts($page);
    }
    
} elseif (strpos($request, '/post/') === 0) {
    // 帖子详情页
    $postId = intval(str_replace('/post/', '', $request));
    displayPost($postId);
    
} elseif (strpos($request, '/admin') === 0) {
    // 后台管理
    include __DIR__ . '/admin/index.php';
    
} else {
    // 首页
    ?>
    <!DOCTYPE html>
    <!-- 前面提供的HTML内容 -->
    <?php
}

function getPosts($page = 1) {
    $data = json_decode(file_get_contents(DATA_DIR . '/posts.json'), true);
    $perPage = $data['per_page'] ?? 10;
    $total = $data['total'] ?? count($data['posts']);
    $posts = array_slice($data['posts'], ($page - 1) * $perPage, $perPage);
    
    return json_encode([
        'posts' => $posts,
        'total' => $total,
        'per_page' => $perPage
    ]);
}

function displayPost($id) {
    $data = json_decode(file_get_contents(DATA_DIR . '/posts.json'), true);
    $post = null;
    
    foreach ($data['posts'] as $p) {
        if ($p['id'] == $id) {
            $post = $p;
            break;
        }
    }
    
    if ($post) {
        // 增加浏览量
        $post['views']++;
        // 更新数据
        foreach ($data['posts'] as &$p) {
            if ($p['id'] == $id) {
                $p = $post;
                break;
            }
        }
        file_put_contents(DATA_DIR . '/posts.json', json_encode($data, JSON_PRETTY_PRINT));
        
        // 显示帖子
        ?>
        <!DOCTYPE html>
        <html>
        <head>
            <title><?= htmlspecialchars($post['title']) ?></title>
            <link rel="stylesheet" href="/forum/assets/css/style.css">
        </head>
        <body>
            <header>
                <h1><?= htmlspecialchars($post['title']) ?></h1>
                <a href="/forum">返回列表</a>
            </header>
            
            <article>
                <?= nl2br(htmlspecialchars($post['content'])) ?>
            </article>
            
            <div class="meta">
                <span>作者: <?= htmlspecialchars($post['author']) ?></span>
                <span>日期: <?= htmlspecialchars($post['date']) ?></span>
                <span>浏览: <?= $post['views'] ?></span>
            </div>
        </body>
        </html>
        <?php
    } else {
        http_response_code(404);
        echo '帖子不存在';
    }
}

6. 后台管理 (admin/index.php)

<?php
// 简单的身份验证
session_start();
if (!isset($_SESSION['admin_logged_in'])) {
    header('Location: /forum/admin/login.php');
    exit;
}

// 后台首页
?>
<!DOCTYPE html>
<html>
<head>
    <title>论坛管理</title>
    <link rel="stylesheet" href="/forum/assets/css/admin.css">
</head>
<body>
    <header>
        <h1>论坛管理</h1>
        <nav>
            <a href="/forum/admin/">首页</a>
            <a href="/forum/admin/settings.php">采集设置</a>
            <a href="/forum/admin/collect.php">执行采集</a>
            <a href="/forum/admin/logout.php">退出</a>
        </nav>
    </header>
    
    <div class="container">
        <h2>系统概览</h2>
        <?php
        $posts = json_decode(file_get_contents(DATA_DIR . '/posts.json'), true);
        $settings = json_decode(file_get_contents(DATA_DIR . '/settings.json'), true);
        ?>
        <p>帖子总数: <?= count($posts['posts']) ?></p>
        <p>最后采集时间: <?= $settings['last_collect'] ?? '从未采集' ?></p>
    </div>
</body>
</html>

7. 采集功能 (admin/collect.php)

<?php
session_start();
if (!isset($_SESSION['admin_logged_in'])) {
    header('Location: /forum/admin/login.php');
    exit;
}

// 执行采集
$settings = json_decode(file_get_contents(DATA_DIR . '/settings.json'), true);
$posts = json_decode(file_get_contents(DATA_DIR . '/posts.json'), true);

// 获取目标页面内容
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $settings['target_url']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$html = curl_exec($ch);
curl_close($ch);

// 匹配帖子列表
preg_match_all('/' . $settings['list_rule']['pattern'] . '/' . $settings['list_rule']['modifiers'], $html, $matches, PREG_SET_ORDER);

$newPosts = [];
foreach ($matches as $match) {
    $url = $match[1];
    $title = $match[2];
    
    // 获取帖子内容
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $postHtml = curl_exec($ch);
    curl_close($ch);
    
    // 匹配内容
    preg_match('/' . $settings['content_rule']['pattern'] . '/' . $settings['content_rule']['modifiers'], $postHtml, $contentMatch);
    $content = $contentMatch[1] ?? '';
    
    // 添加到新帖子数组
    $newPosts[] = [
        'id' => count($posts['posts']) + count($newPosts) + 1,
        'title' => $title,
        'content' => $content,
        'author' => '采集用户',
        'date' => date('Y-m-d H:i:s'),
        'views' => 0
    ];
}

// 合并新旧帖子
$posts['posts'] = array_merge($newPosts, $posts['posts']);
$posts['total'] = count($posts['posts']);

// 保存数据
file_put_contents(DATA_DIR . '/posts.json', json_encode($posts, JSON_PRETTY_PRINT));

// 更新采集时间
$settings['last_collect'] = date('Y-m-d H:i:s');
file_put_contents(DATA_DIR . '/settings.json', json_encode($settings, JSON_PRETTY_PRINT));

echo "采集完成,新增 " . count($newPosts) . " 条帖子";
?>

8. 伪静态规则

IIS伪静态规则 (web.config)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Forum Posts">
                    <match url="^post/([0-9]+)/?$" />
                    <action type="Rewrite" url="index.php" />
                </rule>
                <rule name="Forum API">
                    <match url="^api/(.*)$" />
                    <action type="Rewrite" url="index.php" />
                </rule>
                <rule name="Admin Panel">
                    <match url="^admin/?(.*)$" />
                    <action type="Rewrite" url="admin/index.php" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

宝塔面板伪静态规则 (Nginx)

location / {
    try_files $uri $uri/ /index.php?$args;
}

location /post/ {
    rewrite ^/post/([0-9]+)/?$ /index.php last;
}

location /api/ {
    rewrite ^/api/(.*)$ /index.php last;
}

location /admin/ {
    rewrite ^/admin/?(.*)$ /admin/index.php last;
}

Apache伪静态规则 (.htaccess)

RewriteEngine On

# 帖子详情
RewriteRule ^post/([0-9]+)/?$ index.php [L]

# API路由
RewriteRule ^api/(.*)$ index.php [L]

# 后台管理
RewriteRule ^admin/?(.*)$ admin/index.php [L]

9. CSS样式 (assets/css/style.css)

/* 基础样式 */
body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    color: #333;
}

header {
    background: #333;
    color: #fff;
    padding: 1rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

header a {
    color: #fff;
    text-decoration: none;
    margin-left: 1rem;
}

#forum-container {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 1rem;
}

.post {
    border-bottom: 1px solid #eee;
    padding: 1rem 0;
}

.post h3 {
    margin: 0 0 0.5rem 0;
}

.post .meta {
    font-size: 0.8rem;
    color: #666;
    margin-top: 0.5rem;
}

.post .meta span {
    margin-right: 1rem;
}

.pagination {
    margin: 2rem 0;
    text-align: center;
}

.pagination a, .pagination span {
    display: inline-block;
    padding: 0.5rem 1rem;
    margin: 0 0.2rem;
    border: 1px solid #ddd;
    text-decoration: none;
    color: #333;
}

.pagination a:hover {
    background: #f5f5f5;
}

.pagination .current {
    background: #333;
    color: #fff;
    border-color: #333;
}

/* 后台样式 */
.container {
    max-width: 1000px;
    margin: 2rem auto;
    padding: 0 1rem;
}

自动采集规则搜索实现

要实现自动搜索采集规则,可以在后台添加一个规则发现功能:

// admin/find_rules.php
function findListRules($html) {
    // 尝试发现常见的列表模式
    $commonPatterns = [
        '/<div class="item">.*?<a href="(.*?)".*?>(.*?)<\/a>.*?<\/div>/s',
        '/<li>.*?<a href="(.*?)".*?>(.*?)<\/a>.*?<\/li>/s',
        '/<article>.*?<h2>.*?<a href="(.*?)".*?>(.*?)<\/a>.*?<\/h2>.*?<\/article>/s'
    ];
    
    foreach ($commonPatterns as $pattern) {
        if (preg_match_all($pattern, $html, $matches) && count($matches[0]) > 0) {
            return [
                'pattern' => str_replace('/', '\/', substr($pattern, 1, -1)),
                'modifiers' => 's'
            ];
        }
    }
    
    return null;
}

function findContentRules($html) {
    // 尝试发现常见的内容模式
    $commonPatterns = [
        '/<div class="content">(.*?)<\/div>/s',
        '/<article.*?>(.*?)<\/article>/s',
        '/<div class="post-content">(.*?)<\/div>/s'
    ];
    
    foreach ($commonPatterns as $pattern) {
        if (preg_match($pattern, $html, $matches) && strlen($matches[1]) > 50) {
            return [
                'pattern' => str_replace('/', '\/', substr($pattern, 1, -1)),
                'modifiers' => 's'
            ];
        }
    }
    
    return null;
}

部署说明

  1. 将代码上传到服务器
  2. 确保PHP版本 >= 7.0
  3. 创建data目录并设置可写权限 (chmod 777 data)
  4. 初始化posts.json和settings.json文件
  5. 根据服务器类型配置伪静态规则
  6. 访问/admin设置采集规则

安全注意事项

  1. 为后台管理添加强密码验证
  2. 限制采集频率防止被封
  3. 对采集的内容进行过滤防止XSS攻击
  4. 定期备份data目录下的JSON文件

这个实现提供了一个完整的单页论坛系统,包含了所有你要求的功能。你可以根据实际需求进行调整和扩展。 更多详情:baijiahao.baidu.com/s?id=183050…