PHP项目怎样实现资讯点赞功能?

wen PHP项目 18

本文目录导读:

PHP项目怎样实现资讯点赞功能?

  1. 基础功能设计
  2. 性能优化方案
  3. 安全性考虑
  4. 完整示例:点赞API接口
  5. 前端UI示例

我来详细介绍在PHP项目中实现资讯点赞功能的几种方案,从简单到完善逐步展开。

基础功能设计

数据库表结构

-- 资讯文章表
CREATE TABLE `articles` (
    `id` int(11) NOT NULL AUTO_INCREMENT, varchar(200) NOT NULL,
    `content` text,
    `like_count` int(11) DEFAULT '0',
    `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 点赞记录表
CREATE TABLE `article_likes` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `article_id` int(11) NOT NULL,
    `user_id` int(11) NOT NULL,
    `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_article_user` (`article_id`, `user_id`),
    KEY `idx_article_id` (`article_id`),
    KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PHP后端代码

<?php
class LikeService {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 点赞/取消点赞
     */
    public function toggleLike($articleId, $userId) {
        try {
            $this->db->beginTransaction();
            // 检查是否已点赞
            $sql = "SELECT id FROM article_likes 
                    WHERE article_id = ? AND user_id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$articleId, $userId]);
            $existing = $stmt->fetch();
            if ($existing) {
                // 已点赞,取消点赞
                $sql = "DELETE FROM article_likes WHERE id = ?";
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$existing['id']]);
                // 减少点赞数
                $sql = "UPDATE articles SET like_count = like_count - 1 
                        WHERE id = ? AND like_count > 0";
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$articleId]);
                $liked = false;
            } else {
                // 未点赞,添加点赞
                $sql = "INSERT INTO article_likes (article_id, user_id) VALUES (?, ?)";
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$articleId, $userId]);
                // 增加点赞数
                $sql = "UPDATE articles SET like_count = like_count + 1 WHERE id = ?";
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$articleId]);
                $liked = true;
            }
            // 获取最新点赞数
            $sql = "SELECT like_count FROM articles WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$articleId]);
            $likeCount = $stmt->fetchColumn();
            $this->db->commit();
            return [
                'success' => true,
                'liked' => $liked,
                'like_count' => $likeCount
            ];
        } catch (Exception $e) {
            $this->db->rollBack();
            return [
                'success' => false,
                'message' => '操作失败'
            ];
        }
    }
    /**
     * 获取用户点赞状态
     */
    public function getUserLikeStatus($articleId, $userId) {
        $sql = "SELECT id FROM article_likes 
                WHERE article_id = ? AND user_id = ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$articleId, $userId]);
        return $stmt->fetch() ? true : false;
    }
}

AJAX前端实现

// 点赞功能
class LikeManager {
    constructor() {
        this.init();
    }
    init() {
        document.querySelectorAll('.like-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                this.handleLike(e.target);
            });
        });
    }
    async handleLike(button) {
        const articleId = button.dataset.articleId;
        const userId = button.dataset.userId;
        try {
            button.disabled = true;
            const response = await fetch('/api/like/toggle', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-Token': this.getCsrfToken()
                },
                body: JSON.stringify({
                    article_id: articleId,
                    user_id: userId
                })
            });
            const result = await response.json();
            if (result.success) {
                // 更新UI
                this.updateUI(button, result);
            }
        } catch (error) {
            console.error('点赞失败:', error);
            alert('操作失败,请重试');
        } finally {
            button.disabled = false;
        }
    }
    updateUI(button, data) {
        // 更新点赞状态
        button.classList.toggle('liked', data.liked);
        // 更新点赞数
        const countElement = button.querySelector('.like-count');
        if (countElement) {
            countElement.textContent = data.like_count;
        }
        // 更新按钮文本
        const textElement = button.querySelector('.like-text');
        if (textElement) {
            textElement.textContent = data.liked ? '已赞' : '点赞';
        }
    }
    getCsrfToken() {
        return document.querySelector('meta[name="csrf-token"]')?.content || '';
    }
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
    new LikeManager();
});

性能优化方案

Redis缓存实现

<?php
class LikeServiceWithCache {
    private $redis;
    private $db;
    private $cacheKey = 'article:like:';
    public function __construct($redis, $db) {
        $this->redis = $redis;
        $this->db = $db;
    }
    /**
     * 异步点赞(Redis + 定时写入DB)
     */
    public function asyncLike($articleId, $userId) {
        $likeKey = $this->cacheKey . $articleId;
        $userKey = $likeKey . ':users';
        // 使用Redis Set存储已点赞用户
        $isLiked = $this->redis->sIsMember($userKey, $userId);
        if ($isLiked) {
            // 取消点赞
            $this->redis->sRem($userKey, $userId);
            $this->redis->decr($likeKey);
        } else {
            // 点赞
            $this->redis->sAdd($userKey, $userId);
            $this->redis->incr($likeKey);
        }
        // 记录同步任务
        $this->addSyncTask($articleId, $userId, !$isLiked);
        return [
            'liked' => !$isLiked,
            'like_count' => $this->redis->get($likeKey)
        ];
    }
    /**
     * 批量同步到数据库
     */
    public function syncToDatabase() {
        // 获取待同步的点赞数据
        $tasks = $this->redis->lRange('like:sync:queue', 0, -1);
        foreach ($tasks as $task) {
            $data = json_decode($task, true);
            try {
                $this->db->beginTransaction();
                if ($data['action'] === 'add') {
                    $sql = "INSERT INTO article_likes (article_id, user_id) 
                            VALUES (?, ?) 
                            ON DUPLICATE KEY UPDATE created_at = NOW()";
                } else {
                    $sql = "DELETE FROM article_likes 
                            WHERE article_id = ? AND user_id = ?";
                }
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$data['article_id'], $data['user_id']]);
                // 更新文章点赞计数
                $sql = "UPDATE articles SET like_count = (
                            SELECT COUNT(*) FROM article_likes WHERE article_id = ?
                        ) WHERE id = ?";
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$data['article_id'], $data['article_id']]);
                $this->db->commit();
                // 移除已完成的任务
                $this->redis->lPop('like:sync:queue');
            } catch (Exception $e) {
                $this->db->rollBack();
                // 记录失败日志
                error_log("Like sync failed: " . $e->getMessage());
            }
        }
    }
    private function addSyncTask($articleId, $userId, $isLike) {
        $task = json_encode([
            'article_id' => $articleId,
            'user_id' => $userId,
            'action' => $isLike ? 'add' : 'remove',
            'timestamp' => time()
        ]);
        $this->redis->rPush('like:sync:queue', $task);
    }
}

防重复点击处理

// 防重复点击
class LikeButton {
    constructor(button) {
        this.button = button;
        this.isProcessing = false;
        this.debounceTimer = null;
    }
    handleClick() {
        if (this.isProcessing) return;
        // 防抖处理,300ms内只处理一次
        clearTimeout(this.debounceTimer);
        this.debounceTimer = setTimeout(() => {
            this.isProcessing = true;
            this.sendLikeRequest();
        }, 300);
    }
    async sendLikeRequest() {
        try {
            // 发送请求
            const response = await fetch(this.button.dataset.url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    article_id: this.button.dataset.articleId
                })
            });
            const result = await response.json();
            this.updateState(result);
        } catch (error) {
            console.error('点赞失败:', error);
            // 恢复状态
            this.button.classList.remove('liked');
        } finally {
            this.isProcessing = false;
        }
    }
}

SQL优化

-- 创建联合索引
CREATE INDEX idx_like_status ON article_likes (article_id, user_id);
-- 使用EXISTS优化查询
SELECT EXISTS(
    SELECT 1 FROM article_likes 
    WHERE article_id = ? AND user_id = ?
) AS is_liked;
-- 批量更新点赞数
UPDATE articles a
SET a.like_count = (
    SELECT COUNT(*) FROM article_likes l 
    WHERE l.article_id = a.id
)
WHERE a.id IN (1, 2, 3); -- 指定需要更新的文章

安全性考虑

CSRF防护

<?php
// 生成CSRF Token
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// 验证CSRF Token
function validateCsrfToken($token) {
    if (!isset($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) {
        http_response_code(403);
        die('CSRF token validation failed');
    }
    return true;
}

请求频率限制

<?php
class RateLimiter {
    private $redis;
    private $limit = 10; // 每分钟最多10次
    private $window = 60; // 时间窗口(秒)
    public function __construct($redis) {
        $this->redis = $redis;
    }
    public function checkLimit($userId, $action = 'like') {
        $key = "rate_limit:{$action}:{$userId}";
        $count = $this->redis->incr($key);
        if ($count === 1) {
            $this->redis->expire($key, $this->window);
        }
        return $count <= $this->limit;
    }
}

输入验证

<?php
// 参数验证
function validateLikeRequest($articleId, $userId) {
    // 验证ID是否为正整数
    if (!filter_var($articleId, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]])) {
        throw new InvalidArgumentException('无效的文章ID');
    }
    if (!filter_var($userId, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]])) {
        throw new InvalidArgumentException('无效的用户ID');
    }
    // 检查文章是否存在
    $article = getArticle($articleId);
    if (!$article) {
        throw new Exception('文章不存在');
    }
    return true;
}

完整示例:点赞API接口

<?php
// /api/like/toggle.php
header('Content-Type: application/json');
require_once 'config.php';
require_once 'LikeService.php';
// 验证登录状态
session_start();
if (!isset($_SESSION['user_id'])) {
    http_response_code(401);
    echo json_encode(['error' => '请先登录']);
    exit;
}
// 验证CSRF Token
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $input = json_decode(file_get_contents('php://input'), true);
    try {
        // 验证输入
        $articleId = filter_var($input['article_id'], FILTER_VALIDATE_INT);
        $userId = $_SESSION['user_id'];
        if (!$articleId) {
            throw new Exception('参数错误');
        }
        // 频率限制检查
        $rateLimiter = new RateLimiter($redis);
        if (!$rateLimiter->checkLimit($userId)) {
            throw new Exception('操作太频繁,请稍后再试');
        }
        // 执行点赞操作
        $likeService = new LikeService($db);
        $result = $likeService->toggleLike($articleId, $userId);
        echo json_encode($result);
    } catch (Exception $e) {
        http_response_code(400);
        echo json_encode([
            'success' => false,
            'message' => $e->getMessage()
        ]);
    }
}

前端UI示例

<!-- 点赞按钮HTML -->
<button class="like-btn <?= $isLiked ? 'liked' : '' ?>" 
        data-article-id="<?= $article['id'] ?>"
        data-user-id="<?= $_SESSION['user_id'] ?>">
    <span class="like-icon">
        <svg viewBox="0 0 24 24" width="16" height="16">
            <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 
                     2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09
                     C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5
                     c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
        </svg>
    </span>
    <span class="like-text"><?= $isLiked ? '已赞' : '点赞' ?></span>
    <span class="like-count"><?= $article['like_count'] ?></span>
</button>
<style>
.like-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    border: 1px solid #ddd;
    border-radius: 20px;
    background: #fff;
    cursor: pointer;
    transition: all 0.3s ease;
    font-size: 14px;
}
.like-btn:hover {
    background: #f0f0f0;
}
.like-btn.liked {
    background: #ff6b6b;
    color: #fff;
    border-color: #ff6b6b;
}
.like-btn.liked .like-icon svg {
    fill: #fff;
}
.like-icon svg {
    fill: #666;
    transition: fill 0.3s ease;
}
.like-btn .like-count {
    font-weight: 600;
}
/* 点赞动画 */
.like-btn.liked .like-icon {
    animation: likeAnim 0.3s ease;
}
@keyframes likeAnim {
    0% { transform: scale(1); }
    50% { transform: scale(1.2); }
    100% { transform: scale(1); }
}
</style>

这个实现方案涵盖了:

  • 基础功能:点赞/取消点赞
  • 性能优化:Redis缓存、异步同步
  • 安全性:CSRF防护、频率限制、输入验证
  • 用户体验:防抖处理、动画效果

根据项目规模选择合适的实现方式,小型项目直接用数据库即可,高并发场景建议使用Redis缓存方案。

抱歉,评论功能暂时关闭!