本文目录导读:

我来详细介绍如何在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);
}
}
这个勋章系统实现了:
- 完整的CRUD操作 - 创建、读取、更新、删除勋章
- 条件检测机制 - 自动检测用户行为并授予勋章
- 进度追踪 - 记录用户完成进度
- 事件驱动 - 通过事件监听自动触发
- 管理后台 - 批量授予、统计分析
- 前端展示 - 勋章墙、进度条、排行榜
- 扩展性 - 支持自定义条件和连锁成就
根据项目需要,还可以添加:
- 勋章动画效果
- 社交分享功能
- 勋章交易系统
- 特殊勋章(限时、节日等)
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播