Sample Markdown
This is # 积分系统 Spec
Why
Xiuno BBS 用户表已有 credits/golds/rmbs 三个积数字段,但缺少统一的服务层管理、变更日志、防刷机制和 API 接口。需要一个健壮的积分系统来支持多积分类型的增减操作、审计日志、防刷限制和插件扩展。
What Changes
- 新建
bbs_credits_log 积分日志表(含 type, change, reason, ip, time)
- 新建
lib/CreditsService.php 统一积分服务类(add/sub/get/log/checkNegative,行锁+事务,禁止负分)
- 新建
api/v1/credits.php REST API 路由(GET 查询 / POST 增加扣减),需 Bearer token 鉴权
- 新增插件钩子:
credits_before_change / credits_after_change
- 新增防刷机制:同一 reason + 用户每日限制次数(可配置)
- 新建
cron/clean_credits_log.php 日志清理定时任务(保留最近90天)
- 更新
UpgradeService.php 添加积分系统升级步骤
- 更新
api/v1/bootstrap.php 注册 credits 路由
- 更新
conf/conf.default.php 添加积分相关配置项
- 更新
update.md 记录变更
Impact
- Affected code:
lib/CreditsService.php(新增), api/v1/credits.php(新增), api/v1/bootstrap.php(修改), lib/UpgradeService.php(修改), conf/conf.default.php(修改), cron/clean_credits_log.php(新增)
- 数据库: 新建
bbs_credits_log 表,用户表已有 credits/golds/rmbs 字段无需修改
- 兼容性: 不覆盖 Xiuno 原有积分函数,使用新的 CreditsService 类
ADDED Requirements
Requirement: 积分日志表
系统 SHALL 创建 bbs_credits_log 表,包含字段:logid(AI主键), uid, type(积分类型: credits/golds/rmbs), change(变动值,正负), balance(变动后余额), reason(变动原因), ip, create_date。索引:(uid, create_date), (uid, reason, create_date)。
Scenario: 记录积分变动
- WHEN 用户积分发生变动
- THEN 系统在 credits_log 表中插入一条记录,包含变动类型、变动值、变动后余额、原因、IP 和时间
Requirement: CreditsService 统一服务类
系统 SHALL 提供 CreditsService 类,包含方法:add($uid, $type, $amount, $reason)、sub($uid, $type, $amount, $reason)、get($uid, $type)、log($uid, $page, $pagesize)、checkNegative($uid, $type, $amount)。所有写操作使用行锁(SELECT FOR UPDATE)+ 事务,禁止余额为负。
Scenario: 增加积分
- WHEN 调用
CreditsService::add() 增加积分
- THEN 使用行锁读取当前余额,增加指定金额,写入日志,提交事务
Scenario: 扣减积分防止负分
- WHEN 调用
CreditsService::sub() 扣减积分且余额不足
- THEN 事务回滚,抛出异常或返回错误,余额不变
Scenario: 并发安全
- WHEN 同一用户同时发生多笔积分变动
- THEN 行锁确保串行执行,余额计算正确
Requirement: 积分 API 路由
系统 SHALL 提供 REST API 端点:
GET /api/v1/credits — 查询当前用户积分余额(需 Bearer token)
GET /api/v1/credits/log — 查询当前用户积分日志(分页)
POST /api/v1/credits/add — 增加积分(管理员或指定 reason 允许的操作)
POST /api/v1/credits/sub — 扣减积分(管理员或指定 reason 允许的操作)
所有端点需 Bearer token 鉴权,普通用户只能操作自己的积分。
Scenario: 普通用户查询自己的积分
- WHEN 已登录用户 GET /api/v1/credits
- THEN 返回该用户所有积分类型的余额
Scenario: 普通用户操作他人积分被拒绝
- WHEN 普通用户 POST /api/v1/credits/add 指定 uid 不是自己
- THEN 返回 403 权限不足错误
Scenario: 管理员操作任意用户积分
- WHEN 管理员 POST /api/v1/credits/add 指定任意 uid
- THEN 成功增加积分
Requirement: 插件钩子
系统 SHALL 在积分变动前后提供钩子:
credits_before_change — 变动前触发,可阻止操作或修改变动值
credits_after_change — 变动后触发,用于通知、统计等
Scenario: 钩子阻止积分变动
- WHEN credits_before_change 钩子返回 false
- THEN 积分变动被阻止,事务回滚
Scenario: 钩子修改变动值
- WHEN credits_before_change 钩子修改 amount 值
- THEN 使用修改后的 amount 执行积分变动
Requirement: 防刷机制
系统 SHALL 对同一 reason + uid 的每日操作次数进行限制,次数阈值通过配置设定(credits_daily_limit)。超出限制时拒绝操作。
Scenario: 防刷限制生效
- WHEN 同一用户同一天对同一 reason 的操作次数超过配置限制
- THEN 返回错误,拒绝本次积分变动
Scenario: 防刷限制未超
- WHEN 同一用户同一天对同一 reason 的操作次数未超过限制
- THEN 正常执行积分变动
Requirement: 日志清理定时任务
系统 SHALL 提供 cron/clean_credits_log.php 脚本,删除 90 天前的积分日志,保留天数可通过配置调整(credits_log_retention_days)。
Scenario: 清理过期日志
- WHEN 运行清理脚本
- THEN 删除 create_date 早于保留天数之前的所有日志记录
Requirement: 升级脚本
系统 SHALL 在 UpgradeService 中添加积分系统升级步骤,创建 credits_log 表并添加积分相关配置项。
Scenario: 从旧版升级
- WHEN 执行升级流程
- THEN 自动创建 credits_log 表,添加 credits_daily_limit 和 credits_log_retention_days 配置项
MODIFIED Requirements
Requirement: API 路由注册
修改 api/v1/bootstrap.php,在路由分发中新增 credits 资源的路由指向 credits.php,在端点列表中添加 credits。
Requirement: 系统配置
后台设置新增配置项:
credits_daily_limit — 每日防刷限制次数(默认 10)
credits_log_retention_days — 日志保留天数(默认 90)
credits_types — 启用的积分类型列表(默认 ['credits', 'golds', 'rmbs'])