单页论坛实现方案(PHP+JS+CSS+JSON)

51 阅读4分钟

单页论坛实现方案(PHP+JS+CSS+JSON)

下面我将提供一个完整的单页论坛实现方案,包含前端展示、后端数据处理、伪静态配置和自动采集功能。

系统架构

  • 前端:HTML + CSS + JavaScript (使用jQuery简化DOM操作)
  • 后端:PHP处理数据
  • 数据存储:JSON文件
  • 伪静态:IIS和宝塔规则
  • 自动采集:PHP定时任务

文件结构

/forum/
├── index.php            # 主入口文件
├── api/
│   ├── get_posts.php    # 获取帖子API
│   ├── add_post.php     # 添加帖子API
│   └── collect.php      # 采集API
├── data/
│   ├── posts.json       # 帖子数据存储
│   └── config.json      # 配置信息
├── css/
│   └── style.css        # 样式文件
├── js/
│   └── script.js        # 前端逻辑
└── .htaccess            # Apache伪静态规则

1. 前端实现 (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="css/style.css">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="js/script.js"></script>
</head>
<body>
    <div class="forum-container">
        <header>
            <h1>单页论坛</h1>
            <div class="user-panel">
                <button id="login-btn">登录</button>
                <button id="register-btn">注册</button>
            </div>
        </header>
        
        <div class="post-form">
            <h2>发表新帖</h2>
            <form id="new-post-form">
                <input type="text" id="post-title" placeholder="标题" required>
                <textarea id="post-content" placeholder="内容" required></textarea>
                <button type="submit">发布</button>
            </form>
        </div>
        
        <div class="posts-list">
            <h2>最新帖子</h2>
            <div id="posts-container"></div>
            <div class="pagination">
                <button id="prev-page">上一页</button>
                <span id="page-info">第1页</span>
                <button id="next-page">下一页</button>
            </div>
        </div>
        
        <footer>
            <p>© 2023 单页论坛 - 使用PHP+JS+JSON构建</p>
        </footer>
    </div>
</body>
</html>

2. CSS样式 (css/style.css)

body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    background-color: #f5f5f5;
    color: #333;
}

.forum-container {
    max-width: 1000px;
    margin: 0 auto;
    padding: 20px;
    background-color: #fff;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 20px;
    border-bottom: 1px solid #eee;
}

.user-panel button {
    margin-left: 10px;
    padding: 5px 15px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.post-form {
    margin: 20px 0;
    padding: 20px;
    background-color: #f9f9f9;
    border-radius: 5px;
}

.post-form input, .post-form textarea {
    width: 100%;
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

.post-form textarea {
    min-height: 100px;
    resize: vertical;
}

.post-form button {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.post-item {
    padding: 20px;
    margin-bottom: 20px;
    background-color: #f9f9f9;
    border-radius: 5px;
    border-left: 4px solid #4CAF50;
}

.post-title {
    font-size: 1.2em;
    font-weight: bold;
    margin-bottom: 10px;
    color: #333;
}

.post-meta {
    font-size: 0.8em;
    color: #666;
    margin-bottom: 10px;
}

.post-content {
    margin-bottom: 10px;
}

.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 20px;
}

.pagination button {
    padding: 5px 15px;
    margin: 0 10px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.pagination button:disabled {
    background-color: #cccccc;
    cursor: not-allowed;
}

footer {
    margin-top: 30px;
    padding-top: 20px;
    border-top: 1px solid #eee;
    text-align: center;
    color: #666;
    font-size: 0.9em;
}

3. JavaScript逻辑 (js/script.js)

$(document).ready(function() {
    let currentPage = 1;
    const postsPerPage = 10;
    
    // 加载帖子
    function loadPosts(page) {
        $.get('api/get_posts.php', { page: page, per_page: postsPerPage }, function(data) {
            $('#posts-container').empty();
            
            if (data.posts && data.posts.length > 0) {
                data.posts.forEach(function(post) {
                    const postHtml = `
                        <div class="post-item">
                            <div class="post-title">${post.title}</div>
                            <div class="post-meta">
                                作者: ${post.author} | 时间: ${post.time} | 浏览量: ${post.views}
                            </div>
                            <div class="post-content">${post.content}</div>
                        </div>
                    `;
                    $('#posts-container').append(postHtml);
                });
                
                $('#page-info').text(`第${page}页 / 共${data.totalPages}页`);
                $('#prev-page').prop('disabled', page <= 1);
                $('#next-page').prop('disabled', page >= data.totalPages);
            } else {
                $('#posts-container').html('<p>暂无帖子</p>');
            }
            
            currentPage = page;
        }, 'json');
    }
    
    // 初始化加载第一页
    loadPosts(currentPage);
    
    // 分页按钮事件
    $('#prev-page').click(function() {
        if (currentPage > 1) {
            loadPosts(currentPage - 1);
        }
    });
    
    $('#next-page').click(function() {
        loadPosts(currentPage + 1);
    });
    
    // 提交新帖子
    $('#new-post-form').submit(function(e) {
        e.preventDefault();
        
        const title = $('#post-title').val();
        const content = $('#post-content').val();
        
        if (title && content) {
            $.post('api/add_post.php', {
                title: title,
                content: content,
                author: '匿名用户'
            }, function(response) {
                if (response.success) {
                    $('#post-title').val('');
                    $('#post-content').val('');
                    loadPosts(1); // 刷新到第一页
                } else {
                    alert('发布失败: ' + response.message);
                }
            }, 'json');
        }
    });
    
    // 自动采集按钮(管理员可见)
    $('#collect-btn').click(function() {
        $.get('api/collect.php', function(response) {
            if (response.success) {
                alert('采集成功,新增' + response.count + '条帖子');
                loadPosts(1); // 刷新到第一页
            } else {
                alert('采集失败: ' + response.message);
            }
        }, 'json');
    });
});

4. PHP后端API

api/get_posts.php

<?php
header('Content-Type: application/json');

$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$perPage = isset($_GET['per_page']) ? max(1, intval($_GET['per_page'])) : 10;

$dataFile = '../data/posts.json';

if (!file_exists($dataFile)) {
    file_put_contents($dataFile, json_encode(['posts' => []]));
}

$data = json_decode(file_get_contents($dataFile), true);
$posts = $data['posts'] ?? [];

// 按时间降序排序
usort($posts, function($a, $b) {
    return strtotime($b['time']) - strtotime($a['time']);
});

$totalPosts = count($posts);
$totalPages = ceil($totalPosts / $perPage);

$start = ($page - 1) * $perPage;
$paginatedPosts = array_slice($posts, $start, $perPage);

echo json_encode([
    'success' => true,
    'posts' => $paginatedPosts,
    'totalPages' => $totalPages,
    'currentPage' => $page
]);
?>

api/add_post.php

<?php
header('Content-Type: application/json');

$title = isset($_POST['title']) ? trim($_POST['title']) : '';
$content = isset($_POST['content']) ? trim($_POST['content']) : '';
$author = isset($_POST['author']) ? trim($_POST['author']) : '匿名用户';

if (empty($title) || empty($content)) {
    echo json_encode(['success' => false, 'message' => '标题和内容不能为空']);
    exit;
}

$dataFile = '../data/posts.json';

if (!file_exists($dataFile)) {
    file_put_contents($dataFile, json_encode(['posts' => []]));
}

$data = json_decode(file_get_contents($dataFile), true);

$newPost = [
    'id' => uniqid(),
    'title' => $title,
    'content' => nl2br(htmlspecialchars($content)),
    'author' => htmlspecialchars($author),
    'time' => date('Y-m-d H:i:s'),
    'views' => 0
];

array_unshift($data['posts'], $newPost);

file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT));

echo json_encode(['success' => true, 'message' => '发布成功']);
?>

api/collect.php (自动采集)

<?php
header('Content-Type: application/json');

// 简单的权限验证
if (!isset($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST']) === false) {
    echo json_encode(['success' => false, 'message' => '无权访问']);
    exit;
}

// 模拟从外部网站采集数据
function collectFromExternalSite() {
    // 这里应该是实际的采集逻辑
    // 示例中我们只是模拟一些随机数据
    
    $titles = [
        'PHP最新版本发布了',
        'JavaScript框架比较',
        'CSS Grid布局教程',
        'Web开发趋势分析',
        '如何优化网站性能'
    ];
    
    $contents = [
        'PHP 8.2 带来了许多新特性,包括...',
        'React, Vue和Angular是目前最流行的前端框架...',
        'CSS Grid是一种强大的布局系统...',
        '2023年Web开发的主要趋势包括...',
        '网站性能优化可以从多个方面入手...'
    ];
    
    $authors = ['技术小编', 'Web开发者', '前端工程师', 'PHP程序员', '网站管理员'];
    
    $count = rand(1, 3); // 每次采集1-3篇文章
    $collectedPosts = [];
    
    for ($i = 0; $i < $count; $i++) {
        $collectedPosts[] = [
            'title' => $titles[array_rand($titles)],
            'content' => $contents[array_rand($contents)],
            'author' => $authors[array_rand($authors)],
            'time' => date('Y-m-d H:i:s', time() - rand(0, 86400)),
            'views' => rand(0, 100)
        ];
    }
    
    return $collectedPosts;
}

$dataFile = '../data/posts.json';

if (!file_exists($dataFile)) {
    file_put_contents($dataFile, json_encode(['posts' => []]));
}

$data = json_decode(file_get_contents($dataFile), true);
$newPosts = collectFromExternalSite();
$addedCount = 0;

foreach ($newPosts as $post) {
    // 避免重复添加
    $exists = false;
    foreach ($data['posts'] as $existingPost) {
        if ($existingPost['title'] == $post['title']) {
            $exists = true;
            break;
        }
    }
    
    if (!$exists) {
        $post['id'] = uniqid();
        array_unshift($data['posts'], $post);
        $addedCount++;
    }
}

file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT));

echo json_encode([
    'success' => true,
    'count' => $addedCount,
    'message' => '采集完成'
]);
?>

5. 伪静态配置

IIS伪静态规则 (web.config)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Forum Pagination" stopProcessing="true">
                    <match url="^forum/page/([0-9]+)/?$" />
                    <action type="Rewrite" url="index.php?page={R:1}" />
                </rule>
                <rule name="Forum Post" stopProcessing="true">
                    <match url="^forum/post/([a-zA-Z0-9-]+)/?$" />
                    <action type="Rewrite" url="index.php?action=post&id={R:1}" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

宝塔伪静态规则 (Nginx)

在宝塔面板的网站设置中添加以下伪静态规则:

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

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

location /forum/post/([a-zA-Z0-9-]+)/ {
    rewrite ^/forum/post/([a-zA-Z0-9-]+)/?$ /index.php?action=post&id=$1 last;
}

Apache伪静态规则 (.htaccess)

RewriteEngine On

# 分页URL
RewriteRule ^forum/page/([0-9]+)/?$ index.php?page=$1 [L,QSA]

# 帖子详情URL
RewriteRule ^forum/post/([a-zA-Z0-9-]+)/?$ index.php?action=post&id=$1 [L,QSA]

# 如果请求的不是真实文件或目录,重写到index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [L,QSA]

6. 自动采集的定时任务

在Linux服务器上,可以使用crontab设置定时任务:

# 编辑crontab
crontab -e

# 添加以下行,表示每天凌晨3点执行采集
0 3 * * * /usr/bin/php /path/to/your/forum/api/collect.php >/dev/null 2>&1

对于Windows服务器,可以使用任务计划程序来设置定时执行collect.php。

7. 安全增强建议

  1. 输入过滤:对所有用户输入进行过滤和转义
  2. CSRF防护:添加CSRF令牌以防止跨站请求伪造
  3. 速率限制:对API请求进行速率限制
  4. 数据备份:定期备份posts.json文件
  5. 管理员验证:为采集功能添加管理员登录验证

8. 扩展功能

  1. 用户系统:添加用户注册、登录功能
  2. 帖子分类:为帖子添加分类标签
  3. 搜索功能:实现帖子搜索
  4. Markdown支持:支持Markdown格式的帖子内容
  5. 图片上传:允许用户上传图片

这个实现方案提供了一个完整的单页论坛系统,包含了前端展示、后端处理、伪静态支持和自动采集功能。您可以根据实际需求进行修改和扩展。 更多详情baijiahao.baidu.com/s?id=183050…