Files
Mr.Xia 0fd011fa1e 功能:实现 Danding_Points 积分系统插件
- 新增积分系统插件,支持积分查询、签到、转账等核心功能
- 包含对应的测试脚本

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 00:24:00 +08:00

206 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Danding Points 插件
全局积分/虚拟货币服务层,为其他插件提供统一的积分管理能力。用户在一个插件中获得的积分可以在另一个插件中消费。
本插件不包含任何用户交互命令(无 NoneBot matcher纯 API 服务层,供其他插件直接 import 调用。
## 目录结构
```
danding_points/
├── __init__.py # 插件元数据 & points_api 单例导出
├── config.py # 配置类
├── database.py # SQLite 数据库操作
└── api.py # PointsAPI 核心
```
## 配置选项
`.env` 文件或 NoneBot 配置文件中添加(均带 `DANDING_` 前缀):
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `DANDING_POINTS_DB_FILE` | str | `data/danding_points/points.db` | 数据库文件路径 |
| `DANDING_POINTS_MAX_BALANCE` | int | `0` | 用户积分余额上限,`0` = 无限制 |
| `DANDING_POINTS_MAX_PER_OPERATION` | int | `0` | 单次操作积分上限,`0` = 无限制 |
| `DANDING_POINTS_LOG_RETENTION_DAYS` | int | `365` | 流水日志保留天数 |
## API 接口
所有方法均为 `async`,通过 `points_api` 单例调用。
### `get_balance(user_id: str) -> int`
查询用户积分余额。用户不存在时自动返回 `0`,不会创建账户。
```python
balance = await points_api.get_balance("123456")
```
### `add_points(user_id, amount, source, reason=None) -> Tuple[bool, int]`
为用户增加积分。用户不存在时自动建户。
- `amount`: 正整数,必须 > 0
- `source`: 来源标识,不能为空(用于流水追踪)
- `reason`: 变动原因,可选
- 返回 `(success, new_balance)`,失败时 `success=False`
触发校验:操作上限 → 余额上限。任一校验失败均返回 `(False, 当前余额)`
```python
ok, balance = await points_api.add_points("123456", 100, "gacha", "抽卡奖励")
if ok:
print(f"充值成功,当前余额: {balance}")
```
### `spend_points(user_id, amount, source, reason=None) -> Tuple[bool, int]`
消费用户积分。余额不足时返回 `(False, 当前余额)`
- `amount`: 正整数,必须 > 0
- 流水中 `amount` 记录为负数
```python
ok, balance = await points_api.spend_points("123456", 30, "shop", "购买道具")
if not ok:
print("余额不足")
```
### `set_points(user_id, amount, source, reason=None) -> Tuple[bool, int]`
直接设定用户积分。**绕过** 操作上限和余额上限约束。
- `amount`: 非负整数,必须 >= 0
- 新值等于旧值时不写流水,直接返回 `(True, amount)`
- `total_earned` 仅累加正向差额(设低不影响)
```python
ok, balance = await points_api.set_points("123456", 500, "admin", "管理员调整")
```
### `get_transactions(user_id, limit=20, offset=0) -> List[dict]`
查询用户积分流水记录,按时间倒序。
- `limit`: 1~100超出范围自动裁剪
- `offset`: 分页偏移量,>= 0
返回字段:`id, user_id, amount, balance_after, source, reason, created_at`
```python
txs = await points_api.get_transactions("123456", limit=10, offset=0)
for tx in txs:
print(f"{tx['created_at']} {tx['amount']:+d} 余额:{tx['balance_after']}")
```
### `get_ranking(limit=10, order_by="points") -> List[dict]`
查询积分排行榜。
- `limit`: 1~100
- `order_by`: `"points"`(按余额)或 `"total_earned"`(按累计获得),其他值回退为 `"points"`
- 使用 `RANK()` 窗口函数,同分并列,次级排序按 `user_id` 字母序
返回字段:`rank, user_id, points, total_earned, total_spent`
```python
ranking = await points_api.get_ranking(limit=10, order_by="points")
for r in ranking:
print(f"#{r['rank']} {r['user_id']} {r['points']}分")
```
## 其他插件对接
### 基本用法
在需要积分功能的插件中 import 单例即可:
```python
from nonebot import require
require("danding_points")
from danding_bot.plugins.danding_points import points_api
```
### 示例:抽卡插件发放奖励
```python
# 在 onmyoji_gacha 插件中
from danding_bot.plugins.danding_points import points_api
async def reward_user(user_id: str):
ok, balance = await points_api.add_points(
user_id, 50, "onmyoji_gacha", "SSR 抽到奖励"
)
return balance
```
### 示例:商店插件消费积分
```python
# 在 shop 插件中
from danding_bot.plugins.danding_points import points_api
async def buy_item(user_id: str, cost: int):
ok, balance = await points_api.spend_points(
user_id, cost, "shop", "购买商品"
)
if not ok:
return "积分不足"
return f"购买成功,剩余 {balance} 积分"
```
### 示例:管理员插件调整积分
```python
# 在 danding_api 插件中
from danding_bot.plugins.danding_points import points_api
async def admin_set(user_id: str, amount: int):
ok, balance = await points_api.set_points(
user_id, amount, "danding_api", "管理员手动调整"
)
return balance
```
### source 命名建议
`source` 参数用于标识积分变动来源,建议各插件使用自身插件名作为 source
| 插件 | source 值 |
|------|-----------|
| onmyoji_gacha | `"onmyoji_gacha"` |
| danding_api | `"danding_api"` |
| shop | `"shop"` |
| sign_in | `"sign_in"` |
## 数据库
使用 SQLite数据文件位于 `data/danding_points/points.db`,无需额外配置。
### 表结构
**user_points** — 用户积分账户
| 字段 | 类型 | 说明 |
|------|------|------|
| user_id | TEXT PK | 用户 ID |
| points | INTEGER | 当前余额,>= 0 |
| total_earned | INTEGER | 累计获得 |
| total_spent | INTEGER | 累计消费 |
| created_at | TEXT | 创建时间 |
| updated_at | TEXT | 最后更新时间 |
**point_transactions** — 积分变动流水
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER PK | 自增 ID |
| user_id | TEXT | 用户 ID |
| amount | INTEGER | 变动数额(消费为负) |
| balance_after | INTEGER | 变动后余额 |
| source | TEXT | 来源标识 |
| reason | TEXT | 变动原因 |
| created_at | TEXT | 创建时间 |