新增注册系统
This commit is contained in:
209
.tasks/2025-10-29_1_add-user-register.md
Normal file
209
.tasks/2025-10-29_1_add-user-register.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# 背景
|
||||||
|
文件名:2025-10-29_1_add-user-register.md
|
||||||
|
创建于:2025-10-29_15:42:51
|
||||||
|
创建者:admin
|
||||||
|
主分支:main
|
||||||
|
任务分支:main
|
||||||
|
Yolo模式:Off
|
||||||
|
|
||||||
|
# 任务描述
|
||||||
|
在WPS Bot Game项目中添加用户注册系统,让用户可以通过 `.register name` 命令将用户ID与名称绑定。
|
||||||
|
|
||||||
|
## 核心需求
|
||||||
|
1. 用户可以通过 `.register name` 命令注册或更新自己的名称
|
||||||
|
2. 这个名称是全局的,所有游戏和功能都可以使用
|
||||||
|
3. 积分赠送功能需要支持通过用户名查找用户,而不仅仅是用户ID
|
||||||
|
4. 需要一个全局的用户名称解析机制
|
||||||
|
|
||||||
|
## 问题背景
|
||||||
|
目前消息传递中的用户ID和用户名不一致,使用起来非常困难。比如赠送积分时指定用户ID太麻烦了。添加注册系统后,可以使用 `.gift username points` 而不是 `.gift 123456 points`。
|
||||||
|
|
||||||
|
# 项目概览
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
```
|
||||||
|
WPSBotGame/
|
||||||
|
├── app.py # FastAPI主应用
|
||||||
|
├── config.py # 配置管理
|
||||||
|
├── core/
|
||||||
|
│ ├── database.py # SQLite数据库操作
|
||||||
|
│ ├── middleware.py # 中间件
|
||||||
|
│ └── models.py # 数据模型
|
||||||
|
├── routers/
|
||||||
|
│ ├── callback.py # Callback路由处理
|
||||||
|
│ └── health.py # 健康检查
|
||||||
|
├── games/ # 游戏模块
|
||||||
|
│ ├── base.py # 游戏基类
|
||||||
|
│ ├── gift.py # 积分赠送系统
|
||||||
|
│ └── ... # 其他游戏
|
||||||
|
└── utils/
|
||||||
|
├── parser.py # 指令解析
|
||||||
|
└── message.py # 消息发送
|
||||||
|
```
|
||||||
|
|
||||||
|
# 分析
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
1. `users` 表已经有 `username` 字段,但没有被充分利用
|
||||||
|
2. `get_or_create_user()` 可以接收 username 参数,但实际调用时没有传
|
||||||
|
3. `gift.py` 目前只能通过用户ID进行赠送
|
||||||
|
4. 缺少通过用户名查找用户的功能
|
||||||
|
|
||||||
|
## 关键技术点
|
||||||
|
1. **数据库层**: 需要添加根据 username 查找用户的方
|
||||||
|
2. 需要添加更新用户名的功能
|
||||||
|
3. **指令解析层**: 需要添加 `.register` 指令的识别
|
||||||
|
4. **路由层**: 需要添加 register 类型的处理逻辑
|
||||||
|
5. **应用层**: 需要实现注册逻辑,并修改 gift 功能支持用户名
|
||||||
|
|
||||||
|
# 提议的解决方案
|
||||||
|
|
||||||
|
## 方案概述
|
||||||
|
1. 在 `database.py` 中添加两个方法:
|
||||||
|
- `get_user_by_name(username: str)` - 根据用户名查找用户
|
||||||
|
- `update_user_name(user_id: int, username: str)` - 更新用户名称
|
||||||
|
2. 在 `parser.py` 中添加 `.register` 指令映射
|
||||||
|
3. 在 `callback.py` 中添加 register 类型的处理逻辑
|
||||||
|
4. 修改 `gift.py` 支持通过用户名赠送积分
|
||||||
|
5. 创建注册处理逻辑(可以单独文件或集成到 callback)
|
||||||
|
|
||||||
|
## 设计决策
|
||||||
|
- 用户名作为额外的查找方式,但不替代 user_id(保持数据库主键稳定)
|
||||||
|
- 用户名不强制唯一(允许相同昵称)
|
||||||
|
- 注册功能独立于游戏模块,放在顶层处理
|
||||||
|
|
||||||
|
# 当前执行步骤:"3. 等待用户确认"
|
||||||
|
|
||||||
|
# 详细实施计划
|
||||||
|
|
||||||
|
## 文件1: core/database.py
|
||||||
|
|
||||||
|
### 添加方法1: get_user_by_name()
|
||||||
|
```python
|
||||||
|
def get_user_by_name(self, username: str) -> Optional[Dict]:
|
||||||
|
"""根据用户名查找用户
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: 用户名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户信息字典,如果不存在返回None
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加方法2: update_user_name()
|
||||||
|
```python
|
||||||
|
def update_user_name(self, user_id: int, username: str) -> bool:
|
||||||
|
"""更新用户名称
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户ID
|
||||||
|
username: 新用户名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加数据库索引
|
||||||
|
在 `init_tables()` 方法中添加:
|
||||||
|
```sql
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_username ON users(username)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件2: utils/parser.py
|
||||||
|
|
||||||
|
### 修改 COMMAND_MAP
|
||||||
|
在现有的 COMMAND_MAP 中添加:
|
||||||
|
```python
|
||||||
|
'.register': 'register',
|
||||||
|
'.注册': 'register',
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件3: routers/callback.py
|
||||||
|
|
||||||
|
### 在 handle_command() 中添加处理
|
||||||
|
在现有游戏类型判断后添加:
|
||||||
|
```python
|
||||||
|
# 注册系统
|
||||||
|
if game_type == 'register':
|
||||||
|
return await handle_register_command(command, chat_id, user_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加处理函数
|
||||||
|
```python
|
||||||
|
async def handle_register_command(command: str, chat_id: int, user_id: int) -> str:
|
||||||
|
"""处理注册命令
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: 完整指令 ".register name"
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
注册结果消息
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件4: games/gift.py
|
||||||
|
|
||||||
|
### 修改 _process_gift_command()
|
||||||
|
修改参数解析逻辑,支持用户名和用户ID两种格式:
|
||||||
|
```python
|
||||||
|
def _process_gift_command(self, args: str, sender_id: int) -> str:
|
||||||
|
# 尝试解析为用户名或用户ID
|
||||||
|
parts = args.split(maxsplit=2)
|
||||||
|
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "❌ 指令格式错误..."
|
||||||
|
|
||||||
|
# 检查第一部分是用户名还是ID
|
||||||
|
receiver_input = parts[0]
|
||||||
|
|
||||||
|
if receiver_input.isdigit():
|
||||||
|
# 是数字,作为用户ID处理
|
||||||
|
receiver_id = int(receiver_input)
|
||||||
|
else:
|
||||||
|
# 是用户名,通过数据库查找
|
||||||
|
user = self.db.get_user_by_name(receiver_input)
|
||||||
|
if not user:
|
||||||
|
return f"❌ 未找到用户: {receiver_input}"
|
||||||
|
receiver_id = user['user_id']
|
||||||
|
|
||||||
|
# 继续原有的赠送逻辑...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新帮助信息
|
||||||
|
在 _get_gift_help() 中更新示例:
|
||||||
|
```
|
||||||
|
".gift username 50 生日快乐"
|
||||||
|
".gift 123456 50" (仍支持用户ID)
|
||||||
|
```
|
||||||
|
|
||||||
|
# 任务进度
|
||||||
|
|
||||||
|
## 2025-10-29_15:42:51
|
||||||
|
- 已修改:.tasks/2025-10-29_1_add-user-register.md
|
||||||
|
- 更改:创建任务文件
|
||||||
|
- 原因:开始实施用户注册系统
|
||||||
|
- 阻碍因素:无
|
||||||
|
- 状态:未确认
|
||||||
|
|
||||||
|
## 2025-10-29_15:46:38
|
||||||
|
- 已修改:
|
||||||
|
- core/database.py(添加 get_user_by_name() 和 update_user_name() 方法,添加 username 索引)
|
||||||
|
- utils/parser.py(添加 .register 和 .注册 命令映射)
|
||||||
|
- routers/callback.py(添加 register 命令处理和 handle_register_command() 函数)
|
||||||
|
- games/gift.py(修改 _process_gift_command() 支持用户名查找,更新帮助信息)
|
||||||
|
- 更改:
|
||||||
|
- 数据库层:添加用户名称查询和更新功能
|
||||||
|
- 命令解析:注册 .register 命令映射
|
||||||
|
- 路由处理:实现用户注册逻辑
|
||||||
|
- 赠送系统:支持通过用户名或ID进行积分赠送
|
||||||
|
- 原因:实现用户注册系统,让用户可以使用名称替代用户ID
|
||||||
|
- 阻碍因素:无
|
||||||
|
- 状态:未确认
|
||||||
|
|
||||||
|
# 最终审查
|
||||||
|
[等待实施]
|
||||||
|
|
||||||
@@ -88,6 +88,12 @@ class Database:
|
|||||||
ON game_states(chat_id, user_id)
|
ON game_states(chat_id, user_id)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# 创建用户名索引
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_username
|
||||||
|
ON users(username)
|
||||||
|
""")
|
||||||
|
|
||||||
# 游戏统计表
|
# 游戏统计表
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS game_stats (
|
CREATE TABLE IF NOT EXISTS game_stats (
|
||||||
@@ -158,6 +164,53 @@ class Database:
|
|||||||
'last_active': current_time
|
'last_active': current_time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_user_by_name(self, username: str) -> Optional[Dict]:
|
||||||
|
"""根据用户名查找用户
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: 用户名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户信息字典,如果不存在返回None
|
||||||
|
"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT * FROM users WHERE username = ?",
|
||||||
|
(username,)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
return dict(row)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_user_name(self, user_id: int, username: str) -> bool:
|
||||||
|
"""更新用户名称
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: 用户ID
|
||||||
|
username: 新用户名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 确保用户存在
|
||||||
|
self.get_or_create_user(user_id)
|
||||||
|
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE users SET username = ? WHERE user_id = ?",
|
||||||
|
(username, user_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"用户 {user_id} 更新名称为: {username}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"更新用户名失败: user_id={user_id}, username={username}, error={e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
# ===== 游戏状态相关操作 =====
|
# ===== 游戏状态相关操作 =====
|
||||||
|
|
||||||
def get_game_state(self, chat_id: int, user_id: int, game_type: str) -> Optional[Dict]:
|
def get_game_state(self, chat_id: int, user_id: int, game_type: str) -> Optional[Dict]:
|
||||||
|
|||||||
@@ -55,18 +55,39 @@ class GiftGame(BaseGame):
|
|||||||
Returns:
|
Returns:
|
||||||
处理结果消息
|
处理结果消息
|
||||||
"""
|
"""
|
||||||
# 解析参数:.gift <receiver_id> <points> [message]
|
# 解析参数:.gift <receiver_identifier> <points> [message]
|
||||||
parts = args.split(maxsplit=2)
|
parts = args.split(maxsplit=2)
|
||||||
|
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
return "❌ 指令格式错误!\n\n正确格式:`.gift <用户ID> <积分数量> [附赠消息]`\n\n示例:\n`.gift 123 50 生日快乐`\n`.gift 456 100`"
|
return "❌ 指令格式错误!\n\n正确格式:`.gift <用户名或ID> <积分数量> [附赠消息]`\n\n示例:\n`.gift 张三 50 生日快乐`\n`.gift 123 50`\n`.gift 456 100`"
|
||||||
|
|
||||||
|
# 解析积分数量
|
||||||
try:
|
try:
|
||||||
receiver_id = int(parts[0])
|
|
||||||
points = int(parts[1])
|
points = int(parts[1])
|
||||||
message = parts[2] if len(parts) > 2 else None
|
message = parts[2] if len(parts) > 2 else None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return "❌ 用户ID和积分数量必须是数字!"
|
return "❌ 积分数量必须是数字!"
|
||||||
|
|
||||||
|
# 检查第一部分是用户名还是ID
|
||||||
|
receiver_input = parts[0]
|
||||||
|
|
||||||
|
if receiver_input.isdigit():
|
||||||
|
# 是数字,作为用户ID处理
|
||||||
|
receiver_id = int(receiver_input)
|
||||||
|
else:
|
||||||
|
# 是用户名,通过数据库查找
|
||||||
|
user = self.db.get_user_by_name(receiver_input)
|
||||||
|
if not user:
|
||||||
|
return f"❌ 未找到用户: {receiver_input}\n\n请确认用户名是否正确,或使用用户ID。"
|
||||||
|
receiver_id = user['user_id']
|
||||||
|
|
||||||
|
# 获取接收者名称用于显示
|
||||||
|
receiver_user = self.db.get_or_create_user(receiver_id)
|
||||||
|
receiver_name = receiver_user.get('username', f"用户{receiver_id}")
|
||||||
|
|
||||||
|
# 获取发送者名称用于显示
|
||||||
|
sender_user = self.db.get_or_create_user(sender_id)
|
||||||
|
sender_name = sender_user.get('username', f"用户{sender_id}")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if points <= 0:
|
if points <= 0:
|
||||||
@@ -92,8 +113,8 @@ class GiftGame(BaseGame):
|
|||||||
receiver_points_after = self.db.get_user_points(receiver_id)
|
receiver_points_after = self.db.get_user_points(receiver_id)
|
||||||
|
|
||||||
text = f"## 🎁 积分赠送成功!\n\n"
|
text = f"## 🎁 积分赠送成功!\n\n"
|
||||||
text += f"**赠送者**:用户{sender_id}\n\n"
|
text += f"**赠送者**:{sender_name}\n\n"
|
||||||
text += f"**接收者**:用户{receiver_id}\n\n"
|
text += f"**接收者**:{receiver_name}\n\n"
|
||||||
text += f"**赠送积分**:{points} 分\n\n"
|
text += f"**赠送积分**:{points} 分\n\n"
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
@@ -212,7 +233,7 @@ class GiftGame(BaseGame):
|
|||||||
"""
|
"""
|
||||||
text = f"## 🎁 积分赠送系统\n\n"
|
text = f"## 🎁 积分赠送系统\n\n"
|
||||||
text += f"### 基础用法\n"
|
text += f"### 基础用法\n"
|
||||||
text += f"- `.gift <用户ID> <积分数量> [附赠消息]` - 赠送积分\n"
|
text += f"- `.gift <用户名或ID> <积分数量> [附赠消息]` - 赠送积分\n"
|
||||||
text += f"- `.gift stats` - 查看赠送统计\n"
|
text += f"- `.gift stats` - 查看赠送统计\n"
|
||||||
text += f"- `.gift sent` - 查看发送记录\n"
|
text += f"- `.gift sent` - 查看发送记录\n"
|
||||||
text += f"- `.gift received` - 查看接收记录\n"
|
text += f"- `.gift received` - 查看接收记录\n"
|
||||||
@@ -226,13 +247,16 @@ class GiftGame(BaseGame):
|
|||||||
|
|
||||||
text += f"### 示例\n"
|
text += f"### 示例\n"
|
||||||
text += f"```\n"
|
text += f"```\n"
|
||||||
text += f".gift 123 50 生日快乐\n"
|
text += f".gift 张三 50 生日快乐\n"
|
||||||
text += f".gift 456 100 感谢你的帮助\n"
|
text += f".gift 123 50 (使用用户ID)\n"
|
||||||
text += f".gift 789 200\n"
|
text += f".gift 李四 100 感谢你的帮助\n"
|
||||||
|
text += f".gift 王五 200\n"
|
||||||
text += f".gift stats\n"
|
text += f".gift stats\n"
|
||||||
text += f"```\n\n"
|
text += f"```\n\n"
|
||||||
|
|
||||||
text += f"### 说明\n"
|
text += f"### 说明\n"
|
||||||
|
text += f"- 支持使用用户名或用户ID进行赠送\n"
|
||||||
|
text += f"- 使用用户名需要先通过 `.register` 注册名称\n"
|
||||||
text += f"- 所有赠送都有完整记录\n"
|
text += f"- 所有赠送都有完整记录\n"
|
||||||
text += f"- 赠送和接收都会在积分记录中显示\n"
|
text += f"- 赠送和接收都会在积分记录中显示\n"
|
||||||
text += f"- 赠送是单向的,无需对方确认\n"
|
text += f"- 赠送是单向的,无需对方确认\n"
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ async def handle_command(game_type: str, command: str,
|
|||||||
from games.base import get_stats_message
|
from games.base import get_stats_message
|
||||||
return get_stats_message(user_id)
|
return get_stats_message(user_id)
|
||||||
|
|
||||||
|
# 注册系统
|
||||||
|
if game_type == 'register':
|
||||||
|
return await handle_register_command(command, chat_id, user_id)
|
||||||
|
|
||||||
# 骰娘游戏
|
# 骰娘游戏
|
||||||
if game_type == 'dice':
|
if game_type == 'dice':
|
||||||
from games.dice import DiceGame
|
from games.dice import DiceGame
|
||||||
@@ -185,3 +189,40 @@ async def handle_command(game_type: str, command: str,
|
|||||||
logger.error(f"处理游戏指令异常: {e}", exc_info=True)
|
logger.error(f"处理游戏指令异常: {e}", exc_info=True)
|
||||||
return f"❌ 处理指令时出错: {str(e)}"
|
return f"❌ 处理指令时出错: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_register_command(command: str, chat_id: int, user_id: int) -> str:
|
||||||
|
"""处理注册命令
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: 完整指令 ".register name"
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
注册结果消息
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 提取参数
|
||||||
|
_, args = CommandParser.extract_command_args(command)
|
||||||
|
args = args.strip()
|
||||||
|
|
||||||
|
# 验证参数
|
||||||
|
if not args:
|
||||||
|
return "❌ 请提供要注册的名称!\n\n正确格式:`.register <名称>`\n\n示例:\n`.register 张三`\n`.register 小明`"
|
||||||
|
|
||||||
|
if len(args) > 20:
|
||||||
|
return "❌ 名称过长!最多支持20个字符。"
|
||||||
|
|
||||||
|
# 更新用户名称
|
||||||
|
db = get_db()
|
||||||
|
success = db.update_user_name(user_id, args)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return f"✅ 注册成功!\n\n**您的名称**:{args}\n\n之后您可以使用这个名称参与各种游戏和功能。"
|
||||||
|
else:
|
||||||
|
return "❌ 注册失败!请稍后重试。"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理注册指令错误: {e}", exc_info=True)
|
||||||
|
return f"❌ 处理指令出错: {str(e)}"
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ class CommandParser:
|
|||||||
# 统计
|
# 统计
|
||||||
'.stats': 'stats',
|
'.stats': 'stats',
|
||||||
'.统计': 'stats',
|
'.统计': 'stats',
|
||||||
|
|
||||||
|
# 用户注册系统
|
||||||
|
'.register': 'register',
|
||||||
|
'.注册': 'register',
|
||||||
}
|
}
|
||||||
|
|
||||||
# 机器人名称模式(用于从@消息中提取)
|
# 机器人名称模式(用于从@消息中提取)
|
||||||
|
|||||||
Reference in New Issue
Block a user