怎样在PHP项目中实现用户勋章系统?

wen java案例 2

本文目录导读:

怎样在PHP项目中实现用户勋章系统?

  1. 数据库设计
  2. 勋章系统核心类
  3. 勋章事件监听器
  4. 勋章管理员类
  5. 使用示例
  6. 前端展示示例
  7. 高级功能扩展

我来详细介绍如何在PHP项目中实现一个完整的用户勋章系统。

数据库设计

首先设计勋章系统的数据库表结构:

-- 勋章定义表
CREATE TABLE badges (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL COMMENT '勋章名称',
    description VARCHAR(255) COMMENT '勋章描述',
    icon VARCHAR(100) COMMENT '勋章图标路径/类名',
    category VARCHAR(50) COMMENT '分类(如: 活动, 成就, 等级)',
    criteria JSON COMMENT '获取条件(存储为JSON)',
    rarity ENUM('common', 'rare', 'epic', 'legendary') DEFAULT 'common',
    is_active TINYINT(1) DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 用户勋章关联表
CREATE TABLE user_badges (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    badge_id INT NOT NULL,
    earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    progress JSON COMMENT '进度记录(如: 已完成50%)',
    is_displayed TINYINT(1) DEFAULT 1,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (badge_id) REFERENCES badges(id),
    UNIQUE KEY unique_user_badge (user_id, badge_id)
);
-- 用户活动日志表(用于检测条件)
CREATE TABLE user_activity_log (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    activity_type VARCHAR(50) COMMENT '活动类型',
    activity_data JSON COMMENT '活动详情',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_activity (user_id, activity_type)
);

勋章系统核心类

<?php
namespace App\Services;
class BadgeSystem
{
    private $db;
    private $badgeRules = [];
    public function __construct($db)
    {
        $this->db = $db;
        $this->loadBadgeRules();
    }
    /**
     * 加载所有勋章规则
     */
    private function loadBadgeRules()
    {
        $this->badgeRules = [
            'first_login' => [
                'class' => 'FirstLoginBadge',
                'handler' => function($userId) {
                    return $this->checkFirstLogin($userId);
                }
            ],
            'login_streak' => [
                'class' => 'LoginStreakBadge',
                'handler' => function($userId, $days) {
                    return $this->checkLoginStreak($userId, $days);
                }
            ],
            'post_count' => [
                'class' => 'PostCountBadge',
                'handler' => function($userId, $count) {
                    return $this->checkPostCount($userId, $count);
                }
            ],
            'comment_count' => [
                'class' => 'CommentCountBadge',
                'handler' => function($userId, $count) {
                    return $this->checkCommentCount($userId, $count);
                }
            ],
            'level_reached' => [
                'class' => 'LevelReachedBadge',
                'handler' => function($userId, $level) {
                    return $this->checkLevelReached($userId, $level);
                }
            ],
            'points_earned' => [
                'class' => 'PointsEarnedBadge',
                'handler' => function($userId, $points) {
                    return $this->checkPointsEarned($userId, $points);
                }
            ]
        ];
    }
    /**
     * 检测并授予勋章
     */
    public function checkAndAward($userId, $eventType, $eventData = [])
    {
        $awardedBadges = [];
        // 获取适用的勋章
        $badges = $this->getApplicableBadges($eventType);
        foreach ($badges as $badge) {
            // 检查用户是否已有该勋章
            if ($this->hasBadge($userId, $badge['id'])) {
                continue;
            }
            // 检测条件是否满足
            $criteria = json_decode($badge['criteria'], true);
            $condition = $criteria['condition'] ?? '';
            $value = $criteria['value'] ?? 0;
            if ($this->evaluateCondition($userId, $condition, $value, $eventData)) {
                $this->awardBadge($userId, $badge['id']);
                $awardedBadges[] = $badge;
            }
        }
        return $awardedBadges;
    }
    /**
     * 获取适用的勋章
     */
    private function getApplicableBadges($eventType)
    {
        $query = "SELECT * FROM badges WHERE is_active = 1";
        // 根据事件类型过滤(这里简化处理)
        $result = $this->db->query($query);
        return $result->fetch_all(MYSQLI_ASSOC);
    }
    /**
     * 评估条件
     */
    private function evaluateCondition($userId, $condition, $value, $eventData)
    {
        switch ($condition) {
            case 'login_count':
                return $this->checkLoginCount($userId) >= $value;
            case 'post_count':
                return $this->checkPostCount($userId) >= $value;
            case 'comment_count':
                return $this->checkCommentCount($userId) >= $value;
            case 'login_streak':
                return $this->checkLoginStreak($userId) >= $value;
            case 'points':
                return $this->getUserPoints($userId) >= $value;
            case 'level':
                return $this->getUserLevel($userId) >= $value;
            case 'register_days':
                return $this->getRegisterDays($userId) >= $value;
            case 'custom_event':
                return $this->checkCustomEvent($userId, $eventData);
            default:
                return false;
        }
    }
    /**
     * 授予勋章
     */
    public function awardBadge($userId, $badgeId)
    {
        $query = "INSERT INTO user_badges (user_id, badge_id, earned_at, is_displayed) 
                  VALUES (?, ?, NOW(), 1)";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("ii", $userId, $badgeId);
        if ($stmt->execute()) {
            // 通知用户
            $this->notifyUser($userId, $badgeId);
            return true;
        }
        return false;
    }
    /**
     * 检查用户是否已有勋章
     */
    public function hasBadge($userId, $badgeId)
    {
        $query = "SELECT id FROM user_badges 
                  WHERE user_id = ? AND badge_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("ii", $userId, $badgeId);
        $stmt->execute();
        return $stmt->get_result()->num_rows > 0;
    }
    /**
     * 获取用户所有勋章
     */
    public function getUserBadges($userId, $includeProgress = false)
    {
        $query = "SELECT b.*, ub.earned_at, ub.is_displayed, ub.progress
                  FROM badges b
                  JOIN user_badges ub ON b.id = ub.badge_id
                  WHERE ub.user_id = ?
                  ORDER BY ub.earned_at DESC";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $badges = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
        if ($includeProgress) {
            foreach ($badges as &$badge) {
                $badge['progress_data'] = json_decode($badge['progress'], true);
            }
        }
        return $badges;
    }
    /**
     * 获取用户勋章统计
     */
    public function getUserBadgeStats($userId)
    {
        $stats = [
            'total' => 0,
            'by_rarity' => [],
            'recent' => []
        ];
        // 总数
        $query = "SELECT COUNT(*) as total FROM user_badges WHERE user_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $stats['total'] = $stmt->get_result()->fetch_assoc()['total'];
        // 按稀有度统计
        $query = "SELECT b.rarity, COUNT(*) as count 
                  FROM user_badges ub 
                  JOIN badges b ON ub.badge_id = b.id 
                  WHERE ub.user_id = ? 
                  GROUP BY b.rarity";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        while ($row = $stmt->get_result()->fetch_assoc()) {
            $stats['by_rarity'][$row['rarity']] = $row['count'];
        }
        // 最近获得的5个勋章
        $query = "SELECT b.*, ub.earned_at 
                  FROM user_badges ub 
                  JOIN badges b ON ub.badge_id = b.id 
                  WHERE ub.user_id = ? 
                  ORDER BY ub.earned_at DESC 
                  LIMIT 5";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $stats['recent'] = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
        return $stats;
    }
    /**
     * 获取所有可获得的勋章(包括未获得的)
     */
    public function getAllBadgesWithProgress($userId)
    {
        $query = "SELECT b.*, ub.earned_at, ub.progress,
                  CASE WHEN ub.id IS NOT NULL THEN 1 ELSE 0 END as is_earned
                  FROM badges b
                  LEFT JOIN user_badges ub ON b.id = ub.badge_id AND ub.user_id = ?
                  WHERE b.is_active = 1
                  ORDER BY b.category, b.name";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
    }
    /**
     * 更新勋章进度
     */
    public function updateBadgeProgress($userId, $badgeId, $progress, $total)
    {
        $progressData = json_encode([
            'current' => $progress,
            'total' => $total,
            'percentage' => ($total > 0) ? round(($progress / $total) * 100, 2) : 0
        ]);
        $query = "UPDATE user_badges SET progress = ? 
                  WHERE user_id = ? AND badge_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("sii", $progressData, $userId, $badgeId);
        return $stmt->execute();
    }
    /**
     * 设置勋章显示状态
     */
    public function toggleBadgeDisplay($userId, $badgeId)
    {
        $query = "UPDATE user_badges 
                  SET is_displayed = NOT is_displayed 
                  WHERE user_id = ? AND badge_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("ii", $userId, $badgeId);
        return $stmt->execute();
    }
    /**
     * 通知用户获得勋章
     */
    private function notifyUser($userId, $badgeId)
    {
        // 获取勋章信息
        $query = "SELECT name, description, icon FROM badges WHERE id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $badgeId);
        $stmt->execute();
        $badge = $stmt->get_result()->fetch_assoc();
        // 创建通知
        $notificationService = new NotificationService($this->db);
        $notificationService->createNotification(
            $userId,
            'badge_earned',
            "恭喜你获得了「{$badge['name']}」勋章!",
            ['badge_id' => $badgeId, 'badge_icon' => $badge['icon']]
        );
    }
    // 辅助检查方法
    private function checkLoginCount($userId)
    {
        $query = "SELECT COUNT(*) as count FROM user_activity_log 
                  WHERE user_id = ? AND activity_type = 'login'";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc()['count'];
    }
    private function checkLoginStreak($userId)
    {
        // 计算连续登录天数
        $query = "SELECT DISTINCT DATE(created_at) as login_date 
                  FROM user_activity_log 
                  WHERE user_id = ? AND activity_type = 'login'
                  ORDER BY login_date DESC";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $streak = 0;
        $expectedDate = new DateTime();
        while ($row = $result->fetch_assoc()) {
            $loginDate = new DateTime($row['login_date']);
            if ($loginDate->format('Y-m-d') === $expectedDate->format('Y-m-d')) {
                $streak++;
                $expectedDate->modify('-1 day');
            } else {
                break;
            }
        }
        return $streak;
    }
    private function checkPostCount($userId)
    {
        // 假设有一个posts表
        $query = "SELECT COUNT(*) as count FROM posts WHERE user_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc()['count'];
    }
    private function getUserPoints($userId)
    {
        $query = "SELECT SUM(points) as total FROM user_points WHERE user_id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc()['total'] ?? 0;
    }
    private function getUserLevel($userId)
    {
        $query = "SELECT level FROM users WHERE id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc()['level'] ?? 1;
    }
    private function getRegisterDays($userId)
    {
        $query = "SELECT DATEDIFF(NOW(), created_at) as days FROM users WHERE id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        return $stmt->get_result()->fetch_assoc()['days'] ?? 0;
    }
}

勋章事件监听器

<?php
namespace App\Services;
class BadgeEventDispatcher
{
    private $badgeSystem;
    private $events = [];
    public function __construct($badgeSystem)
    {
        $this->badgeSystem = $badgeSystem;
        $this->registerEvents();
    }
    private function registerEvents()
    {
        $this->events = [
            'user.login' => 'onUserLogin',
            'user.register' => 'onUserRegister',
            'post.created' => 'onPostCreated',
            'comment.created' => 'onCommentCreated',
            'user.level_up' => 'onLevelUp',
            'user.points_earned' => 'onPointsEarned',
            'user.like_received' => 'onLikeReceived',
            'achievement.completed' => 'onAchievementCompleted'
        ];
    }
    public function dispatch($event, $userId, $data = [])
    {
        if (isset($this->events[$event])) {
            $handler = $this->events[$event];
            $this->$handler($userId, $data);
        }
    }
    private function onUserLogin($userId, $data)
    {
        // 记录登录活动
        $this->logActivity($userId, 'login', $data);
        // 检查登录相关勋章
        $this->badgeSystem->checkAndAward($userId, 'login', $data);
    }
    private function onUserRegister($userId, $data)
    {
        // 注册时检查首次登录勋章
        $this->badgeSystem->checkAndAward($userId, 'register', $data);
    }
    private function onPostCreated($userId, $data)
    {
        // 记录发帖活动
        $this->logActivity($userId, 'post', $data);
        // 检查发帖相关勋章
        $this->badgeSystem->checkAndAward($userId, 'post', $data);
    }
    private function onLevelUp($userId, $data)
    {
        $this->badgeSystem->checkAndAward($userId, 'level', $data);
    }
    private function logActivity($userId, $type, $data)
    {
        $query = "INSERT INTO user_activity_log (user_id, activity_type, activity_data) 
                  VALUES (?, ?, ?)";
        $stmt = $this->badgeSystem->db->prepare($query);
        $activityData = json_encode($data);
        $stmt->bind_param("iss", $userId, $type, $activityData);
        $stmt->execute();
    }
}

勋章管理员类

<?php
namespace App\Admin;
class BadgeManager
{
    private $db;
    public function __construct($db)
    {
        $this->db = $db;
    }
    /**
     * 创建新勋章
     */
    public function createBadge($data)
    {
        $query = "INSERT INTO badges (name, description, icon, category, criteria, rarity) 
                  VALUES (?, ?, ?, ?, ?, ?)";
        $stmt = $this->db->prepare($query);
        $criteria = json_encode($data['criteria']);
        $stmt->bind_param("ssssss", 
            $data['name'], 
            $data['description'], 
            $data['icon'], 
            $data['category'],
            $criteria,
            $data['rarity']
        );
        return $stmt->execute();
    }
    /**
     * 更新勋章
     */
    public function updateBadge($badgeId, $data)
    {
        $fields = [];
        $params = [];
        $types = '';
        foreach (['name', 'description', 'icon', 'category', 'rarity'] as $field) {
            if (isset($data[$field])) {
                $fields[] = "$field = ?";
                $params[] = $data[$field];
                $types .= 's';
            }
        }
        if (isset($data['criteria'])) {
            $fields[] = "criteria = ?";
            $params[] = json_encode($data['criteria']);
            $types .= 's';
        }
        if (!empty($fields)) {
            $params[] = $badgeId;
            $types .= 'i';
            $query = "UPDATE badges SET " . implode(', ', $fields) . " WHERE id = ?";
            $stmt = $this->db->prepare($query);
            $stmt->bind_param($types, ...$params);
            return $stmt->execute();
        }
        return false;
    }
    /**
     * 批量授予勋章
     */
    public function massAward($badgeId, $userIds)
    {
        $success = 0;
        $query = "INSERT IGNORE INTO user_badges (user_id, badge_id, earned_at) VALUES (?, ?, NOW())";
        $stmt = $this->db->prepare($query);
        foreach ($userIds as $userId) {
            $stmt->bind_param("ii", $userId, $badgeId);
            if ($stmt->execute()) {
                $success++;
            }
        }
        return $success;
    }
    /**
     * 获取勋章统计数据
     */
    public function getBadgeStats()
    {
        $stats = [
            'total_badges' => 0,
            'total_awarded' => 0,
            'most_earned' => [],
            'least_earned' => []
        ];
        // 总勋章数
        $query = "SELECT COUNT(*) as total FROM badges WHERE is_active = 1";
        $stats['total_badges'] = $this->db->query($query)->fetch_assoc()['total'];
        // 总授予次数
        $query = "SELECT COUNT(*) as total FROM user_badges";
        $stats['total_awarded'] = $this->db->query($query)->fetch_assoc()['total'];
        // 最多获得的勋章
        $query = "SELECT b.name, COUNT(ub.id) as count 
                  FROM badges b 
                  JOIN user_badges ub ON b.id = ub.badge_id 
                  GROUP BY b.id 
                  ORDER BY count DESC 
                  LIMIT 5";
        $stats['most_earned'] = $this->db->query($query)->fetch_all(MYSQLI_ASSOC);
        // 最少获得的勋章
        $query = "SELECT b.name, COUNT(ub.id) as count 
                  FROM badges b 
                  LEFT JOIN user_badges ub ON b.id = ub.badge_id 
                  GROUP BY b.id 
                  ORDER BY count ASC 
                  LIMIT 5";
        $stats['least_earned'] = $this->db->query($query)->fetch_all(MYSQLI_ASSOC);
        return $stats;
    }
}

使用示例

// 初始化
$badgeSystem = new BadgeSystem($db);
$badgeDispatcher = new BadgeEventDispatcher($badgeSystem);
// 在用户登录时触发
$badgeDispatcher->dispatch('user.login', $userId, ['ip' => $ip]);
// 在用户发帖时触发
$badgeDispatcher->dispatch('post.created', $userId, ['post_id' => $postId]);
// 获取用户勋章墙
$badges = $badgeSystem->getUserBadges($userId, true);
// 获取所有可获得的勋章及进度
$allBadges = $badgeSystem->getAllBadgesWithProgress($userId);
// 管理后台创建新勋章
$badgeManager = new BadgeManager($db);
$badgeManager->createBadge([
    'name' => '内容大师',
    'description' => '发布100篇优质文章',
    'icon' => 'fa-trophy',
    'category' => 'content',
    'criteria' => [
        'condition' => 'post_count',
        'value' => 100
    ],
    'rarity' => 'epic'
]);

前端展示示例

<!-- 用户勋章墙 -->
<div class="badge-wall">
    <?php foreach ($badges as $badge): ?>
    <div class="badge-item <?= $badge['is_displayed'] ? 'displayed' : 'hidden' ?>">
        <div class="badge-icon">
            <i class="fas <?= $badge['icon'] ?>"></i>
            <span class="badge-rarity <?= $badge['rarity'] ?>">
                <?= ucfirst($badge['rarity']) ?>
            </span>
        </div>
        <div class="badge-info">
            <h4><?= $badge['name'] ?></h4>
            <p><?= $badge['description'] ?></p>
        </div>
        <div class="badge-date">
            <?= date('Y-m-d', strtotime($badge['earned_at'])) ?>
        </div>
    </div>
    <?php endforeach; ?>
</div>
<!-- 勋章进度条 -->
<div class="badge-progress">
    <?php foreach ($allBadges as $badge): ?>
    <div class="progress-item <?= $badge['is_earned'] ? 'earned' : 'locked' ?>">
        <div class="badge-icon">
            <i class="fas <?= $badge['icon'] ?>"></i>
        </div>
        <div class="progress-details">
            <span class="badge-name"><?= $badge['name'] ?></span>
            <?php if (!$badge['is_earned'] && $badge['progress']): ?>
            <div class="progress-bar">
                <?php $progress = json_decode($badge['progress'], true); ?>
                <div class="progress-fill" style="width: <?= $progress['percentage'] ?>%"></div>
            </div>
            <span class="progress-text">
                <?= $progress['current'] ?>/<?= $progress['total'] ?>
            </span>
            <?php endif; ?>
        </div>
    </div>
    <?php endforeach; ?>
</div>

高级功能扩展

1 勋章成就系统

class AchievementBadgeSystem extends BadgeSystem
{
    public function checkAchievementChain($userId, $badgeId)
    {
        // 检查是否需要完成前置勋章
        $query = "SELECT prerequisites FROM badges WHERE id = ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $badgeId);
        $stmt->execute();
        $badge = $stmt->get_result()->fetch_assoc();
        if ($badge && !empty($badge['prerequisites'])) {
            $prerequisites = json_decode($badge['prerequisites'], true);
            foreach ($prerequisites as $prereqId) {
                if (!$this->hasBadge($userId, $prereqId)) {
                    return false; // 前置未完成
                }
            }
        }
        return parent::checkAndAward($userId, 'achievement', ['badge_id' => $badgeId]);
    }
}

2 勋章排行榜

class BadgeLeaderboard
{
    public function getLeaderboard($limit = 50, $timeframe = 'all')
    {
        $timeCondition = '';
        switch ($timeframe) {
            case 'week':
                $timeCondition = "AND ub.earned_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)";
                break;
            case 'month':
                $timeCondition = "AND ub.earned_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)";
                break;
        }
        $query = "SELECT u.id, u.username, COUNT(ub.id) as badge_count,
                  GROUP_CONCAT(b.name SEPARATOR ', ') as badges
                  FROM users u
                  JOIN user_badges ub ON u.id = ub.user_id
                  JOIN badges b ON ub.badge_id = b.id
                  WHERE 1=1 $timeCondition
                  GROUP BY u.id
                  ORDER BY badge_count DESC
                  LIMIT ?";
        $stmt = $this->db->prepare($query);
        $stmt->bind_param("i", $limit);
        $stmt->execute();
        return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
    }
}

这个勋章系统实现了:

  1. 完整的CRUD操作 - 创建、读取、更新、删除勋章
  2. 条件检测机制 - 自动检测用户行为并授予勋章
  3. 进度追踪 - 记录用户完成进度
  4. 事件驱动 - 通过事件监听自动触发
  5. 管理后台 - 批量授予、统计分析
  6. 前端展示 - 勋章墙、进度条、排行榜
  7. 扩展性 - 支持自定义条件和连锁成就

根据项目需要,还可以添加:

  • 勋章动画效果
  • 社交分享功能
  • 勋章交易系统
  • 特殊勋章(限时、节日等)

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