xnx_medal 勋章系统
功能
勋章后台管理:名称、描述、图标(支持上传 JPG/PNG/GIF/WebP)、获取条件、领取成本、是否需审核、排序
10 种内置获取条件:注册天数、凌晨发帖、多版块发帖(兴趣广度)、邀请人数、积分/金币阈值、早期成员、发帖数、被点赞数、版块发帖、签到天数;外加
manual(仅管理员手动颁发)免费 / 付费两种领取模式:付费勋章用积分或金币购买(经 CreditsService 扣费),免费勋章走条件检查
自动发放 / 需审核两种流程:免费勋章条件达标且无需审核 → 自动颁发;需审核 → 提交申请待管理员审核;付费勋章 → 直接购买颁发
佩戴系统:每用户最多同时佩戴 3 个勋章,按 display_order 排序,取消佩戴后自动整理顺序保持连续
前台页面:勋章墙(
/medals)、勋章详情(/medal-{id},含条件进度条)、我的勋章(/medals-my)、用户勋章页(/medals-{uid})勋章展示:回帖用户名后、帖子详情右侧作者卡片、个人主页昵称后展示已佩戴勋章图标(点击跳转勋章详情)
版块勋章门控:可设置版块所需勋章及匹配模式(
any任意一个 /all全部必备),拦截版块浏览、帖子详情、发新主题、回帖、附件下载;管理员组与帖子作者豁免统计排行:最近颁发勋章、勋章数排行 Top 用户、用户勋章统计(已获得 / 佩戴中 / 待审核)
性能优化:CacheHelper 缓存(all_medals / recent_grants / top_users 等)+ 请求级静态缓存避免列表页 N+1;getUserMedals 用 JOIN 一次查询
发现页接入(DiscoverService,可配置图标 / 名称 / 排序)
随附
seed_medals.php测试数据生成脚本(覆盖所有条件类型,可重复执行)
文件结构
plugin/xnx_medal/
├── conf.json
├── install.php / uninstall.php / upgrade.php
├── setting.php + view/htm/setting.htm
├── seed_medals.php # 测试数据生成脚本
├── model/
│ └── MedalService.php # 核心服务(CRUD / 条件检查 / 申请审核 / 授予回收 / 佩戴 / 门控 / 统计)
├── route/
│ ├── medal.php # 勋章墙 / 详情 / 佩戴 / 取消佩戴 / 申请(GET+POST)
│ └── user_medal.php # 我的勋章 / 指定用户勋章页
├── admin/
│ ├── medal.php # 独立后台路由(备用,默认走 setting.php)
│ └── medal_apply.php
├── hook/
│ ├── model_inc_file.php # 注册 MedalService
│ ├── index_route_case_end.php # 注册前台路由 medals / medal
│ ├── admin_index_route_case_end.php
│ ├── forum_top_list_before.php # 版块页门控拦截
│ ├── thread_info_start.php # 帖子详情门控拦截
│ ├── thread_create_get_start.php # 发新主题页 GET 拦截
│ ├── thread_create_thread_start.php # 发新主题 POST 拦截
│ ├── post_get_post.php # 回帖拦截
│ ├── attach_download_start.php # 附件下载门控拦截
│ ├── forum_start.htm / thread_start.htm # 门控不通过时渲染拦截页
│ ├── post_list_inc_username_after.htm # 回帖用户名后展示已佩戴勋章
│ ├── thread_author_card_username_after.htm # 详情作者卡片展示已佩戴勋章
│ ├── user_profile_username_after.htm # 个人主页昵称后展示已佩戴勋章
│ ├── thread_info_end.htm / my_profile_end.htm / user_nav_end.htm
│ ├── more_discover_items_after.htm / header_nav_*.htm # 已迁移,留空占位
│ └── lang_zh_cn_bbs.php / lang_zh_tw_bbs.php / lang_en_us_bbs.php
├── view/htm/
│ ├── setting.htm # 后台设置(勋章管理 / 审核 / 用户勋章 / 版块门控 / 发现页)
│ ├── admin_medal.htm / admin_medal_apply.htm
│ ├── medals_layout.htm / medal_detail_layout.htm / medal_detail.htm
│ ├── medal_wall.htm / medal_card.inc.htm
│ ├── medal_sidebar_left.inc.htm / medal_sidebar_right.inc.htm
│ ├── user_medal.htm / user_medal_list.inc.htm
│ └── medal_access_deny.htm
└── static/js/xo_medal_admin.js
配置流程
后台启用插件(自动建 4 张表:xnx_medal 勋章定义、xo_user_medal 用户关联、xnx_medal_apply 申请记录、xnx_forum_medal 版块门控)
进入「插件设置」→「勋章管理」→ 新增勋章:填写名称、上传图标、选择条件类型并配置条件值、选择领取成本(免费/付费)、是否需要审核
测试前台:访问
/medals勋章墙,满足条件的免费勋章可一键申请(无需审核则自动颁发);付费勋章点击购买扣费后颁发;需审核勋章提交申请测试审核:后台「勋章审核」Tab 处理待审核申请(通过则颁发,拒绝填原因)
测试佩戴:访问
/medals-my,佩戴/取消佩戴勋章(最多 3 个),在帖子页/个人主页查看图标展示测试版块门控:后台「版块门控」Tab 选择版块 → 勾选所需勋章 → 选择匹配模式(任意一个 / 全部必备)→ 保存;用未持有勋章的账号访问该版块验证拦截
可选:后台配置「发现页」展示(图标 / 名称 / 排序)
可选:运行
plugin/xnx_medal/seed_medals.php生成 12 个覆盖全条件类型的测试勋章
扩展说明
本插件提供多个 hook 注入点与 Service 方法,供其他插件扩展条件类型、监听勋章颁发或复用勋章能力。
事件型 hook:xnx_medal_grant_end
触发位置:
MedalService::grantMedal()在写入xo_user_medal并清理缓存之后、return 之前触发时机:勋章被授予(申请自动颁发 / 付费购买 / 审核通过 / 后台手动颁发 均会触发)
可用变量:
$uid(被授予用户)、$medal_id(勋章 ID)、$reason(授予原因)典型用途:被
xnx_feeds等插件监听,用于颁发勋章后推送动态 feed实现方式:其他插件在
plugin/{插件名}/hook/xnx_medal_grant_end.php放置 PHP 代码(以<?php exit;开头)
条件扩展 hook(新增自定义条件类型)
本插件在条件检查 / 进度 / 描述 / 后台表单 4 个环节都预留了 hook,其他插件可注册新的 condition_type:
Hook 文件 | 位置 | 用途 | 期望输出 |
|---|---|---|---|
|
| 自定义条件的达标检查 | 设置 |
|
| 自定义条件的进度数据(详情页进度条) | 设置 |
|
| 自定义条件的文字描述 | 将描述字符串赋给 |
|
| 后台保存时构建自定义条件的 condition_value | 将 JSON 字符串赋给 |
|
| 后台条件类型下拉新增选项 | 输出 |
|
| 后台自定义条件的输入表单 | 输出表单 HTML |
约定:自定义 condition_type 需与上述 6 个 hook 配套实现,否则该条件在检查 / 进度 / 描述 / 后台编辑中会出现缺项。
MedalService 公开方法清单
// 实例与缓存
MedalService::getInstance() // 单例(hook 中使用,避免重复 new)
MedalService::clearCache() // 清除本插件所有缓存
MedalService::getUserMedalsCached($uid) // 用户勋章(带静态缓存,列表页用)
MedalService::getUserWearingMedalsCached($uid) // 用户已佩戴勋章(带静态缓存,最多 3 个)
// 勋章 CRUD
getMedal($id) / getMedalList($page, $pagesize) / getAllMedals() / getAllMedalsMap()
createMedal($data) / updateMedal($id, $data) / deleteMedal($id) // delete 级联删用户关联与申请
// 条件检查
checkCondition($uid, $condition_type, $condition_value, $forum_cache=[]) // 返回 ['ok'=>bool,'message'=>str]
getConditionProgress($uid, $condition_type, $condition_value, $forum_cache=[]) // 返回 ['current','required','label','met']
getConditionDescription($condition_type, $condition_value, $forum_cache=[]) // 返回文字描述
// 领取成本
checkCostBalance($uid, $cost_value) // 检查积分余额是否足够(返回 ['ok','message'])
getCostDescription($cost_type, $cost_value) // 返回费用描述
// 申请与审核
applyMedal($uid, $medal_id) // 申请/购买勋章(付费直接扣费颁发,免费走条件+审核)
auditApply($apply_id, $audit_uid, $status, $reason='') // status: 1 通过 / -1 拒绝
getPendingApplies($page, $pagesize) / getUserApplyList($uid) / getApply($id)
// 授予/回收
grantMedal($uid, $medal_id, $reason='') // 授予(已存在返回 false),触发 xnx_medal_grant_end hook
revokeMedal($uid, $medal_id) // 回收
// 佩戴
wearMedal($uid, $medal_id) // 佩戴(最多 3 个)
unwearMedal($uid, $medal_id) // 取消佩戴(自动整理剩余顺序)
getUserWearingMedals($uid) // 已佩戴勋章(按 display_order 排序,最多 3 个)
// 查询
getUserMedals($uid) // 用户全部勋章(JOIN 一次查询,含勋章详情)
getUserMedalIds($uid) / getUserMedalIdsCached($uid)
// 统计排行
getMedalStats($uid) // ['granted','wearing','pending']
getRecentGrants($limit=5) // 最近颁发(60s 缓存)
getMedalRecentGrants($medal_id, $limit=10)
getTopUsers($limit=5) // 勋章数排行(300s 缓存)
// 版块门控
getForumMedalMap() // 全部门控映射(静态缓存)
getForumRequiredMedals($fid) // 版块所需勋章详情
checkForumAccess($uid, $fid) // 返回 ['ok','message','required_medals','mode']
formatDeniedMessage($fid, $scenario, $access_result) // scenario: post/reply/download/view
setForumMedals($fid, $medal_ids, $mode='any') // 全量替换版块门控配置
clearForumMedalCache()
// 辅助
getCurrencyName($type) // 积分类型显示名(支持后台自定义 credits_name/golds_name)
注:
grantMedal/revokeMedal/wearMedal/unwearMedal/auditApply等写操作内部已调用clearCache()或清理静态缓存,调用方无需手动清缓存。