chore(all): remove unused files

This commit is contained in:
MingxuanGame
2025-08-10 04:39:04 +00:00
parent cf45070c2c
commit b0a7278daf
22 changed files with 0 additions and 3316 deletions

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml

View File

@@ -1,17 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="3.7" />
<item index="1" class="java.lang.String" itemvalue="3.11" />
<item index="2" class="java.lang.String" itemvalue="3.12" />
<item index="3" class="java.lang.String" itemvalue="3.13" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

10
.idea/misc.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="osu_lazer_api" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="uv (osu_lazer_api)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/osu_lazer_api.iml" filepath="$PROJECT_DIR$/.idea/osu_lazer_api.iml" />
</modules>
</component>
</project>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="uv (osu_lazer_api)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,140 +0,0 @@
# Lazer API 数据同步指南
本指南将帮助您将现有的 bancho.py 数据库数据同步到新的 Lazer API 专用表中。
## 文件说明
1. **`migrations_old/add_missing_fields.sql`** - 创建 Lazer API 专用表结构
2. **`migrations_old/sync_legacy_data.sql`** - 数据同步脚本
3. **`sync_data.py`** - 交互式数据同步工具
4. **`quick_sync.py`** - 快速同步脚本(使用项目配置)
## 使用方法
### 方法一:快速同步(推荐)
如果您已经配置好了项目的数据库连接,可以直接使用快速同步脚本:
```bash
python quick_sync.py
```
此脚本会:
1. 自动读取项目配置中的数据库连接信息
2. 创建 Lazer API 专用表结构
3. 同步现有数据到新表
### 方法二:交互式同步
如果需要使用不同的数据库连接配置:
```bash
python sync_data.py
```
此脚本会:
1. 交互式地询问数据库连接信息
2. 检查必要表是否存在
3. 显示详细的同步过程和结果
### 方法三:手动执行 SQL
如果您熟悉 SQL 操作,可以手动执行:
```bash
# 1. 创建表结构
mysql -u username -p database_name < migrations_old/add_missing_fields.sql
# 2. 同步数据
mysql -u username -p database_name < migrations_old/sync_legacy_data.sql
```
## 同步内容
### 创建的新表
- `lazer_user_profiles` - 用户扩展资料
- `lazer_user_countries` - 用户国家信息
- `lazer_user_kudosu` - 用户 Kudosu 统计
- `lazer_user_counts` - 用户各项计数统计
- `lazer_user_statistics` - 用户游戏统计(按模式)
- `lazer_user_achievements` - 用户成就
- `lazer_oauth_tokens` - OAuth 访问令牌
- 其他相关表...
### 同步的数据
1. **用户基本信息**
-`users` 表同步基本资料
- 自动转换时间戳格式
- 设置合理的默认值
2. **游戏统计**
-`stats` 表同步各模式的游戏数据
- 计算命中精度和其他衍生统计
3. **用户成就**
-`user_achievements` 表同步成就数据(如果存在)
## 注意事项
1. **安全性**
- 脚本只会创建新表和插入数据
- 不会修改或删除现有的原始表数据
- 使用 `ON DUPLICATE KEY UPDATE` 避免重复插入
2. **兼容性**
- 兼容现有的 bancho.py 数据库结构
- 支持标准的 osu! 数据格式
3. **性能**
- 大量数据可能需要较长时间
- 建议在维护窗口期间执行
## 故障排除
### 常见错误
1. **"Unknown column" 错误**
```
ERROR 1054: Unknown column 'users.is_active' in 'field list'
```
**解决方案**: 确保先执行了 `add_missing_fields.sql` 创建表结构
2. **"Table doesn't exist" 错误**
```
ERROR 1146: Table 'database.users' doesn't exist
```
**解决方案**: 确认数据库中存在 bancho.py 的原始表
3. **连接错误**
```
ERROR 2003: Can't connect to MySQL server
```
**解决方案**: 检查数据库连接配置和权限
### 验证同步结果
同步完成后,可以执行以下查询验证结果:
```sql
-- 检查同步的用户数量
SELECT COUNT(*) FROM lazer_user_profiles;
-- 查看样本数据
SELECT
u.id, u.name,
lup.playmode, lup.is_supporter,
lus.pp, lus.play_count
FROM users u
LEFT JOIN lazer_user_profiles lup ON u.id = lup.user_id
LEFT JOIN lazer_user_statistics lus ON u.id = lus.user_id AND lus.mode = 'osu'
LIMIT 5;
```
## 支持
如果遇到问题,请:
1. 检查日志文件 `data_sync.log`
2. 确认数据库权限
3. 验证原始表数据完整性

View File

@@ -1,242 +0,0 @@
#!/usr/bin/env python3
"""
osu! API 模拟服务器的示例数据填充脚本
"""
from __future__ import annotations
import asyncio
from datetime import datetime
import random
from app.auth import get_password_hash
from app.database import (
User,
)
from app.database.beatmap import Beatmap
from app.database.beatmapset import Beatmapset
from app.database.score import Score
from app.dependencies.database import create_tables, engine
from app.models.beatmap import BeatmapRankStatus, Genre, Language
from app.models.mods import APIMod
from app.models.score import GameMode, Rank
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
async def create_sample_user():
"""创建示例用户数据"""
async with AsyncSession(engine) as session:
async with session.begin():
# 检查用户是否已存在
result = await session.exec(select(User).where(User.name == "Googujiang"))
result2 = await session.exec(
select(User).where(User.name == "MingxuanGame")
)
existing_user = result.first()
existing_user2 = result2.first()
if existing_user is not None and existing_user2 is not None:
print("示例用户已存在,跳过创建")
return
# 当前时间戳
# current_timestamp = int(time.time())
join_timestamp = int(datetime(2019, 11, 29, 17, 23, 13).timestamp())
last_visit_timestamp = int(datetime(2025, 7, 18, 16, 31, 29).timestamp())
# 创建用户
user = User(
name="Googujiang",
safe_name="googujiang", # 安全用户名(小写)
email="googujiang@example.com",
priv=1, # 默认权限
pw_bcrypt=get_password_hash("password123"), # 使用新的哈希方式
country="JP",
silence_end=0,
donor_end=0,
creation_time=join_timestamp,
latest_activity=last_visit_timestamp,
clan_id=0,
clan_priv=0,
preferred_mode=0, # 0 = osu!
play_style=0,
custom_badge_name=None,
custom_badge_icon=None,
userpage_content="「世界に忘れられた」",
api_key=None,
)
user2 = User(
name="MingxuanGame",
safe_name="mingxuangame", # 安全用户名(小写)
email="mingxuangame@example.com",
priv=1, # 默认权限
pw_bcrypt=get_password_hash("password123"), # 使用新的哈希方式
country="US",
silence_end=0,
donor_end=0,
creation_time=join_timestamp,
latest_activity=last_visit_timestamp,
clan_id=0,
clan_priv=0,
preferred_mode=0, # 0 = osu!
play_style=0,
custom_badge_name=None,
custom_badge_icon=None,
userpage_content="For love and fun!",
api_key=None,
)
session.add(user)
session.add(user2)
print(f"成功创建示例用户: {user.name} (ID: {user.id})")
print(f"安全用户名: {user.safe_name}")
print(f"邮箱: {user.email}")
print(f"国家: {user.country}")
print(f"成功创建示例用户: {user2.name} (ID: {user2.id})")
print(f"安全用户名: {user2.safe_name}")
print(f"邮箱: {user2.email}")
print(f"国家: {user2.country}")
async def create_sample_beatmap_data():
"""创建示例谱面数据"""
async with AsyncSession(engine) as session:
async with session.begin():
user_id = random.randint(1, 1000)
# 检查谱面集是否已存在
statement = select(Beatmapset).where(Beatmapset.id == 1)
result = await session.exec(statement)
existing_beatmapset = result.first()
if existing_beatmapset:
print("示例谱面集已存在,跳过创建")
return existing_beatmapset
# 创建谱面集
beatmapset = Beatmapset(
id=1,
artist="Example Artist",
artist_unicode="Example Artist",
covers=None,
creator="Googujiang",
favourite_count=0,
hype_current=0,
hype_required=0,
nsfw=False,
play_count=0,
preview_url="",
source="",
spotlight=False,
title="Example Song",
title_unicode="Example Song",
user_id=user_id,
video=False,
availability_info=None,
download_disabled=False,
bpm=180.0,
can_be_hyped=False,
discussion_locked=False,
last_updated=datetime.now(),
ranked_date=datetime.now(),
storyboard=False,
submitted_date=datetime.now(),
current_nominations=[],
beatmap_status=BeatmapRankStatus.RANKED,
beatmap_genre=Genre.ANY, # 使用整数表示Genre枚举
beatmap_language=Language.ANY, # 使用整数表示Language枚举
nominations_required=0,
nominations_current=0,
pack_tags=[],
ratings=[],
)
session.add(beatmapset)
# 创建谱面
beatmap = Beatmap(
id=1,
url="",
mode=GameMode.OSU,
beatmapset_id=1,
difficulty_rating=5.5,
beatmap_status=BeatmapRankStatus.RANKED,
total_length=195,
user_id=user_id,
version="Example Difficulty",
checksum="example_checksum",
current_user_playcount=0,
max_combo=1200,
ar=9.0,
cs=4.0,
drain=5.0,
accuracy=8.0,
bpm=180.0,
count_circles=1000,
count_sliders=200,
count_spinners=1,
deleted_at=None,
hit_length=180,
last_updated=datetime.now(),
passcount=10,
playcount=50,
)
session.add(beatmap)
# 创建成绩
score = Score(
id=1,
accuracy=0.9876,
map_md5="example_checksum",
user_id=1,
best_id=1,
build_id=None,
classic_total_score=1234567,
ended_at=datetime.now(),
has_replay=True,
max_combo=1100,
mods=[
APIMod(acronym="HD", settings={}),
APIMod(acronym="DT", settings={}),
],
passed=True,
playlist_item_id=None,
pp=250.5,
preserve=True,
rank=Rank.S,
room_id=None,
gamemode=GameMode.OSU,
started_at=datetime.now(),
total_score=1234567,
type="solo_score",
position=None,
beatmap_id=1,
n300=950,
n100=30,
n50=20,
nmiss=5,
ngeki=150,
nkatu=50,
nlarge_tick_miss=None,
nslider_tail_hit=None,
)
session.add(score)
print(f"成功创建示例谱面集: {beatmapset.title} (ID: {beatmapset.id})")
print(f"成功创建示例谱面: {beatmap.version} (ID: {beatmap.id})")
print(f"成功创建示例成绩: ID {score.id}")
return beatmapset
async def main():
print("开始创建示例数据...")
await create_tables()
await create_sample_user()
await create_sample_beatmap_data()
print("示例数据创建完成!")
# print(f"用户名: {user.name}")
# print("密码: password123")
# print("现在您可以使用这些凭据来测试API了。")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,16 +0,0 @@
-- 创建迁移日志表(如果不存在)
CREATE TABLE IF NOT EXISTS `migration_logs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`version` VARCHAR(50) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`timestamp` DATETIME NOT NULL
);
-- 向 lazer_user_statistics 表添加缺失的字段
ALTER TABLE `lazer_user_statistics`
ADD COLUMN IF NOT EXISTS `rank_highest` INT NULL COMMENT '最高排名' AFTER `grade_a`,
ADD COLUMN IF NOT EXISTS `rank_highest_updated_at` DATETIME NULL COMMENT '最高排名更新时间' AFTER `rank_highest`;
-- 更新日志
INSERT INTO `migration_logs` (`version`, `description`, `timestamp`)
VALUES ('20250719', '向 lazer_user_statistics 表添加缺失的字段', NOW());

View File

@@ -1,421 +0,0 @@
-- Lazer API 专用数据表创建脚本
-- 基于真实 osu! API 返回数据设计的表结构
-- 完全不修改 bancho.py 原有表结构,创建全新的 lazer 专用表
-- ============================================
-- Lazer API 专用扩展表
-- ============================================
-- Lazer 用户扩展信息表
CREATE TABLE IF NOT EXISTS lazer_user_profiles (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
-- 基本状态字段
is_active TINYINT(1) DEFAULT 1 COMMENT '用户是否激活',
is_bot TINYINT(1) DEFAULT 0 COMMENT '是否为机器人账户',
is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否已删除',
is_online TINYINT(1) DEFAULT 1 COMMENT '是否在线',
is_supporter TINYINT(1) DEFAULT 0 COMMENT '是否为支持者',
is_restricted TINYINT(1) DEFAULT 0 COMMENT '是否被限制',
session_verified TINYINT(1) DEFAULT 0 COMMENT '会话是否已验证',
has_supported TINYINT(1) DEFAULT 0 COMMENT '是否曾经支持过',
pm_friends_only TINYINT(1) DEFAULT 0 COMMENT '是否只接受好友私信',
-- 基本资料字段
default_group VARCHAR(50) DEFAULT 'default' COMMENT '默认用户组',
last_visit DATETIME NULL COMMENT '最后访问时间',
join_date DATETIME NULL COMMENT '加入日期',
profile_colour VARCHAR(7) NULL COMMENT '个人资料颜色',
profile_hue INT NULL COMMENT '个人资料色调',
-- 社交媒体和个人资料字段
avatar_url VARCHAR(500) NULL COMMENT '头像URL',
cover_url VARCHAR(500) NULL COMMENT '封面URL',
discord VARCHAR(100) NULL COMMENT 'Discord用户名',
twitter VARCHAR(100) NULL COMMENT 'Twitter用户名',
website VARCHAR(500) NULL COMMENT '个人网站',
title VARCHAR(100) NULL COMMENT '用户称号',
title_url VARCHAR(500) NULL COMMENT '称号链接',
interests TEXT NULL COMMENT '兴趣爱好',
location VARCHAR(100) NULL COMMENT '地理位置',
occupation VARCHAR(100) NULL COMMENT '职业',
-- 游戏相关字段
playmode VARCHAR(10) DEFAULT 'osu' COMMENT '主要游戏模式',
support_level INT DEFAULT 0 COMMENT '支持者等级',
max_blocks INT DEFAULT 100 COMMENT '最大屏蔽数量',
max_friends INT DEFAULT 500 COMMENT '最大好友数量',
post_count INT DEFAULT 0 COMMENT '帖子数量',
-- 页面内容
page_html TEXT NULL COMMENT '个人页面HTML',
page_raw TEXT NULL COMMENT '个人页面原始内容',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API 用户扩展资料表';
-- 用户封面信息表
CREATE TABLE IF NOT EXISTS lazer_user_covers (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
custom_url VARCHAR(500) NULL COMMENT '自定义封面URL',
url VARCHAR(500) NULL COMMENT '封面URL',
cover_id INT NULL COMMENT '封面ID',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户封面信息表';
-- 用户国家信息表
CREATE TABLE IF NOT EXISTS lazer_user_countries (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
code VARCHAR(2) NOT NULL COMMENT '国家代码',
name VARCHAR(100) NOT NULL COMMENT '国家名称',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户国家信息表';
-- 用户 Kudosu 表
CREATE TABLE IF NOT EXISTS lazer_user_kudosu (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
available INT DEFAULT 0 COMMENT '可用 Kudosu',
total INT DEFAULT 0 COMMENT '总 Kudosu',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 Kudosu 表';
-- 用户统计计数表
CREATE TABLE IF NOT EXISTS lazer_user_counts (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
-- 统计计数字段
beatmap_playcounts_count INT DEFAULT 0 COMMENT '谱面游玩次数统计',
comments_count INT DEFAULT 0 COMMENT '评论数量',
favourite_beatmapset_count INT DEFAULT 0 COMMENT '收藏谱面集数量',
follower_count INT DEFAULT 0 COMMENT '关注者数量',
graveyard_beatmapset_count INT DEFAULT 0 COMMENT '坟场谱面集数量',
guest_beatmapset_count INT DEFAULT 0 COMMENT '客串谱面集数量',
loved_beatmapset_count INT DEFAULT 0 COMMENT '被喜爱谱面集数量',
mapping_follower_count INT DEFAULT 0 COMMENT '作图关注者数量',
nominated_beatmapset_count INT DEFAULT 0 COMMENT '提名谱面集数量',
pending_beatmapset_count INT DEFAULT 0 COMMENT '待审核谱面集数量',
ranked_beatmapset_count INT DEFAULT 0 COMMENT 'Ranked谱面集数量',
ranked_and_approved_beatmapset_count INT DEFAULT 0 COMMENT 'Ranked+Approved谱面集数量',
unranked_beatmapset_count INT DEFAULT 0 COMMENT '未Ranked谱面集数量',
scores_best_count INT DEFAULT 0 COMMENT '最佳成绩数量',
scores_first_count INT DEFAULT 0 COMMENT '第一名成绩数量',
scores_pinned_count INT DEFAULT 0 COMMENT '置顶成绩数量',
scores_recent_count INT DEFAULT 0 COMMENT '最近成绩数量',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API 用户统计计数表';
-- 用户游戏风格表 (替代 playstyle JSON)
CREATE TABLE IF NOT EXISTS lazer_user_playstyles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
style VARCHAR(50) NOT NULL COMMENT '游戏风格: mouse, keyboard, tablet, touch',
INDEX idx_user_id (user_id),
UNIQUE KEY unique_user_style (user_id, style),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户游戏风格表';
-- 用户个人资料显示顺序表 (替代 profile_order JSON)
CREATE TABLE IF NOT EXISTS lazer_user_profile_sections (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
section_name VARCHAR(50) NOT NULL COMMENT '部分名称',
display_order INT DEFAULT 0 COMMENT '显示顺序',
INDEX idx_user_id (user_id),
INDEX idx_order (user_id, display_order),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户个人资料显示顺序表';
-- 用户账户历史表 (替代 account_history JSON)
CREATE TABLE IF NOT EXISTS lazer_user_account_history (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
event_type VARCHAR(50) NOT NULL COMMENT '事件类型',
description TEXT COMMENT '事件描述',
length INT COMMENT '持续时间(秒)',
permanent TINYINT(1) DEFAULT 0 COMMENT '是否永久',
event_time DATETIME NOT NULL COMMENT '事件时间',
INDEX idx_user_id (user_id),
INDEX idx_event_time (event_time),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户账户历史表';
-- 用户历史用户名表 (替代 previous_usernames JSON)
CREATE TABLE IF NOT EXISTS lazer_user_previous_usernames (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
username VARCHAR(32) NOT NULL COMMENT '历史用户名',
changed_at DATETIME NOT NULL COMMENT '更改时间',
INDEX idx_user_id (user_id),
INDEX idx_changed_at (changed_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户历史用户名表';
-- 用户月度游戏次数表 (替代 monthly_playcounts JSON)
CREATE TABLE IF NOT EXISTS lazer_user_monthly_playcounts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
start_date DATE NOT NULL COMMENT '月份开始日期',
play_count INT DEFAULT 0 COMMENT '游戏次数',
INDEX idx_user_id (user_id),
INDEX idx_start_date (start_date),
UNIQUE KEY unique_user_month (user_id, start_date),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户月度游戏次数表';
-- 用户最高排名表 (rank_highest)
CREATE TABLE IF NOT EXISTS lazer_user_rank_highest (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
rank_position INT NOT NULL COMMENT '最高排名位置',
updated_at DATETIME NOT NULL COMMENT '更新时间',
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户最高排名表';
-- ============================================
-- OAuth 令牌表 (Lazer API 专用)
-- ============================================
CREATE TABLE IF NOT EXISTS lazer_oauth_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
access_token VARCHAR(255) NOT NULL,
refresh_token VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_access_token (access_token),
INDEX idx_refresh_token (refresh_token),
INDEX idx_expires_at (expires_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API OAuth访问令牌表';
-- ============================================
-- 用户统计数据表 (基于真实 API 数据结构)
-- ============================================
-- 用户主要统计表 (statistics 字段)
CREATE TABLE IF NOT EXISTS lazer_user_statistics (
user_id INT NOT NULL,
mode VARCHAR(10) NOT NULL DEFAULT 'osu' COMMENT '游戏模式: osu, taiko, fruits, mania',
-- 基本命中统计
count_100 INT DEFAULT 0 COMMENT '100分命中数',
count_300 INT DEFAULT 0 COMMENT '300分命中数',
count_50 INT DEFAULT 0 COMMENT '50分命中数',
count_miss INT DEFAULT 0 COMMENT 'Miss数',
-- 等级信息
level_current INT DEFAULT 1 COMMENT '当前等级',
level_progress INT DEFAULT 0 COMMENT '等级进度',
-- 排名信息
global_rank INT NULL COMMENT '全球排名',
global_rank_exp INT NULL COMMENT '全球排名(实验性)',
country_rank INT NULL COMMENT '国家/地区排名',
-- PP 和分数
pp DECIMAL(10,2) DEFAULT 0.00 COMMENT 'Performance Points',
pp_exp DECIMAL(10,2) DEFAULT 0.00 COMMENT 'PP(实验性)',
ranked_score BIGINT DEFAULT 0 COMMENT 'Ranked分数',
hit_accuracy DECIMAL(5,2) DEFAULT 0.00 COMMENT '命中精度',
total_score BIGINT DEFAULT 0 COMMENT '总分数',
total_hits BIGINT DEFAULT 0 COMMENT '总命中数',
maximum_combo INT DEFAULT 0 COMMENT '最大连击',
-- 游戏统计
play_count INT DEFAULT 0 COMMENT '游戏次数',
play_time INT DEFAULT 0 COMMENT '游戏时间(秒)',
replays_watched_by_others INT DEFAULT 0 COMMENT '被观看的Replay次数',
is_ranked TINYINT(1) DEFAULT 0 COMMENT '是否有排名',
-- 成绩等级计数 (grade_counts)
grade_ss INT DEFAULT 0 COMMENT 'SS等级数',
grade_ssh INT DEFAULT 0 COMMENT 'SSH等级数',
grade_s INT DEFAULT 0 COMMENT 'S等级数',
grade_sh INT DEFAULT 0 COMMENT 'SH等级数',
grade_a INT DEFAULT 0 COMMENT 'A等级数',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, mode),
INDEX idx_mode (mode),
INDEX idx_global_rank (global_rank),
INDEX idx_country_rank (country_rank),
INDEX idx_pp (pp),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API 用户游戏统计表';
-- 每日挑战用户统计表 (daily_challenge_user_stats)
CREATE TABLE IF NOT EXISTS lazer_daily_challenge_stats (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
daily_streak_best INT DEFAULT 0 COMMENT '最佳每日连击',
daily_streak_current INT DEFAULT 0 COMMENT '当前每日连击',
last_update DATE NULL COMMENT '最后更新日期',
last_weekly_streak DATE NULL COMMENT '最后周连击日期',
playcount INT DEFAULT 0 COMMENT '游戏次数',
top_10p_placements INT DEFAULT 0 COMMENT 'Top 10% 位置数',
top_50p_placements INT DEFAULT 0 COMMENT 'Top 50% 位置数',
weekly_streak_best INT DEFAULT 0 COMMENT '最佳周连击',
weekly_streak_current INT DEFAULT 0 COMMENT '当前周连击',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='每日挑战用户统计表';
-- 用户团队信息表 (team 字段)
CREATE TABLE IF NOT EXISTS lazer_user_teams (
user_id INT PRIMARY KEY COMMENT '关联 users.id',
team_id INT NOT NULL COMMENT '团队ID',
team_name VARCHAR(100) NOT NULL COMMENT '团队名称',
team_short_name VARCHAR(10) NOT NULL COMMENT '团队简称',
flag_url VARCHAR(500) NULL COMMENT '团队旗帜URL',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户团队信息表';
-- 用户成就表 (user_achievements)
CREATE TABLE IF NOT EXISTS lazer_user_achievements (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
achievement_id INT NOT NULL COMMENT '成就ID',
achieved_at DATETIME NOT NULL COMMENT '获得时间',
INDEX idx_user_id (user_id),
INDEX idx_achievement_id (achievement_id),
INDEX idx_achieved_at (achieved_at),
UNIQUE KEY unique_user_achievement (user_id, achievement_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就表';
-- 用户排名历史表 (rank_history)
CREATE TABLE IF NOT EXISTS lazer_user_rank_history (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
mode VARCHAR(10) NOT NULL DEFAULT 'osu' COMMENT '游戏模式',
day_offset INT NOT NULL COMMENT '天数偏移量(从某个基准日期开始)',
rank_position INT NOT NULL COMMENT '排名位置',
INDEX idx_user_mode (user_id, mode),
INDEX idx_day_offset (day_offset),
UNIQUE KEY unique_user_mode_day (user_id, mode, day_offset),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户排名历史表';
-- Replay 观看次数表 (replays_watched_counts)
CREATE TABLE IF NOT EXISTS lazer_user_replays_watched (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
start_date DATE NOT NULL COMMENT '开始日期',
count INT DEFAULT 0 COMMENT '观看次数',
INDEX idx_user_id (user_id),
INDEX idx_start_date (start_date),
UNIQUE KEY unique_user_date (user_id, start_date),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户Replay观看次数表';
-- 用户徽章表 (badges)
CREATE TABLE IF NOT EXISTS lazer_user_badges (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
badge_id INT NOT NULL COMMENT '徽章ID',
awarded_at DATETIME NULL COMMENT '授予时间',
description TEXT NULL COMMENT '徽章描述',
image_url VARCHAR(500) NULL COMMENT '徽章图片URL',
url VARCHAR(500) NULL COMMENT '徽章链接',
INDEX idx_user_id (user_id),
INDEX idx_badge_id (badge_id),
UNIQUE KEY unique_user_badge (user_id, badge_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户徽章表';
-- 用户组表 (groups)
CREATE TABLE IF NOT EXISTS lazer_user_groups (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
group_id INT NOT NULL COMMENT '用户组ID',
group_name VARCHAR(100) NOT NULL COMMENT '用户组名称',
group_identifier VARCHAR(50) NULL COMMENT '用户组标识符',
colour VARCHAR(7) NULL COMMENT '用户组颜色',
is_probationary TINYINT(1) DEFAULT 0 COMMENT '是否为试用期',
has_listing TINYINT(1) DEFAULT 1 COMMENT '是否显示在列表中',
has_playmodes TINYINT(1) DEFAULT 0 COMMENT '是否有游戏模式',
INDEX idx_user_id (user_id),
INDEX idx_group_id (group_id),
UNIQUE KEY unique_user_group (user_id, group_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户组表';
-- 锦标赛横幅表 (active_tournament_banners)
CREATE TABLE IF NOT EXISTS lazer_user_tournament_banners (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
tournament_id INT NOT NULL COMMENT '锦标赛ID',
image_url VARCHAR(500) NOT NULL COMMENT '横幅图片URL',
is_active TINYINT(1) DEFAULT 1 COMMENT '是否为当前活跃横幅',
INDEX idx_user_id (user_id),
INDEX idx_tournament_id (tournament_id),
INDEX idx_is_active (is_active),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户锦标赛横幅表';
-- ============================================
-- 占位表 (未来功能扩展用)
-- ============================================
-- 当前赛季统计占位表
CREATE TABLE IF NOT EXISTS lazer_current_season_stats (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '关联 users.id',
season_id VARCHAR(50) NOT NULL COMMENT '赛季ID',
data_placeholder TEXT COMMENT '赛季数据占位',
INDEX idx_user_id (user_id),
INDEX idx_season_id (season_id),
UNIQUE KEY unique_user_season (user_id, season_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='当前赛季统计占位表';
-- 其他功能占位表
CREATE TABLE IF NOT EXISTS lazer_feature_placeholder (
id INT AUTO_INCREMENT PRIMARY KEY,
feature_type VARCHAR(50) NOT NULL COMMENT '功能类型',
entity_id INT NOT NULL COMMENT '实体ID',
data_placeholder TEXT COMMENT '功能数据占位',
INDEX idx_feature_type (feature_type),
INDEX idx_entity_id (entity_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='功能扩展占位表';

View File

@@ -1,486 +0,0 @@
create table achievements
(
id int auto_increment
primary key,
file varchar(128) not null,
name varchar(128) charset utf8 not null,
`desc` varchar(256) charset utf8 not null,
cond varchar(64) not null,
constraint achievements_desc_uindex
unique (`desc`),
constraint achievements_file_uindex
unique (file),
constraint achievements_name_uindex
unique (name)
);
create table channels
(
id int auto_increment
primary key,
name varchar(32) not null,
topic varchar(256) not null,
read_priv int default 1 not null,
write_priv int default 2 not null,
auto_join tinyint(1) default 0 not null,
constraint channels_name_uindex
unique (name)
);
create index channels_auto_join_index
on channels (auto_join);
create table clans
(
id int auto_increment
primary key,
name varchar(16) charset utf8 not null,
tag varchar(6) charset utf8 not null,
owner int not null,
created_at datetime not null,
constraint clans_name_uindex
unique (name),
constraint clans_owner_uindex
unique (owner),
constraint clans_tag_uindex
unique (tag)
);
create table client_hashes
(
userid int not null,
osupath char(32) not null,
adapters char(32) not null,
uninstall_id char(32) not null,
disk_serial char(32) not null,
latest_time datetime not null,
occurrences int default 0 not null,
primary key (userid, osupath, adapters, uninstall_id, disk_serial)
);
create table comments
(
id int auto_increment
primary key,
target_id int not null comment 'replay, map, or set id',
target_type enum('replay', 'map', 'song') not null,
userid int not null,
time int not null,
comment varchar(80) charset utf8 not null,
colour char(6) null comment 'rgb hex string'
);
create table favourites
(
userid int not null,
setid int not null,
created_at int default 0 not null,
primary key (userid, setid)
);
create table ingame_logins
(
id int auto_increment
primary key,
userid int not null,
ip varchar(45) not null comment 'maxlen for ipv6',
osu_ver date not null,
osu_stream varchar(11) not null,
datetime datetime not null
);
create table relationships
(
user1 int not null,
user2 int not null,
type enum('friend', 'block') not null,
primary key (user1, user2)
);
create table logs
(
id int auto_increment
primary key,
`from` int not null comment 'both from and to are playerids',
`to` int not null,
`action` varchar(32) not null,
msg varchar(2048) charset utf8 null,
time datetime not null on update CURRENT_TIMESTAMP
);
create table mail
(
id int auto_increment
primary key,
from_id int not null,
to_id int not null,
msg varchar(2048) charset utf8 not null,
time int null,
`read` tinyint(1) default 0 not null
);
create table maps
(
server enum('osu!', 'private') default 'osu!' not null,
id int not null,
set_id int not null,
status int not null,
md5 char(32) not null,
artist varchar(128) charset utf8 not null,
title varchar(128) charset utf8 not null,
version varchar(128) charset utf8 not null,
creator varchar(19) charset utf8 not null,
filename varchar(256) charset utf8 not null,
last_update datetime not null,
total_length int not null,
max_combo int not null,
frozen tinyint(1) default 0 not null,
plays int default 0 not null,
passes int default 0 not null,
mode tinyint(1) default 0 not null,
bpm float(12,2) default 0.00 not null,
cs float(4,2) default 0.00 not null,
ar float(4,2) default 0.00 not null,
od float(4,2) default 0.00 not null,
hp float(4,2) default 0.00 not null,
diff float(6,3) default 0.000 not null,
primary key (server, id),
constraint maps_id_uindex
unique (id),
constraint maps_md5_uindex
unique (md5)
);
create index maps_set_id_index
on maps (set_id);
create index maps_status_index
on maps (status);
create index maps_filename_index
on maps (filename);
create index maps_plays_index
on maps (plays);
create index maps_mode_index
on maps (mode);
create index maps_frozen_index
on maps (frozen);
create table mapsets
(
server enum('osu!', 'private') default 'osu!' not null,
id int not null,
last_osuapi_check datetime default CURRENT_TIMESTAMP not null,
primary key (server, id),
constraint nmapsets_id_uindex
unique (id)
);
create table map_requests
(
id int auto_increment
primary key,
map_id int not null,
player_id int not null,
datetime datetime not null,
active tinyint(1) not null
);
create table performance_reports
(
scoreid bigint(20) unsigned not null,
mod_mode enum('vanilla', 'relax', 'autopilot') default 'vanilla' not null,
os varchar(64) not null,
fullscreen tinyint(1) not null,
fps_cap varchar(16) not null,
compatibility tinyint(1) not null,
version varchar(16) not null,
start_time int not null,
end_time int not null,
frame_count int not null,
spike_frames int not null,
aim_rate int not null,
completion tinyint(1) not null,
identifier varchar(128) null comment 'really don''t know much about this yet',
average_frametime int not null,
primary key (scoreid, mod_mode)
);
create table ratings
(
userid int not null,
map_md5 char(32) not null,
rating tinyint(2) not null,
primary key (userid, map_md5)
);
create table scores
(
id bigint unsigned auto_increment
primary key,
map_md5 char(32) not null,
score int not null,
pp float(7,3) not null,
acc float(6,3) not null,
max_combo int not null,
mods int not null,
n300 int not null,
n100 int not null,
n50 int not null,
nmiss int not null,
ngeki int not null,
nkatu int not null,
grade varchar(2) default 'N' not null,
status tinyint not null,
mode tinyint not null,
play_time datetime not null,
time_elapsed int not null,
client_flags int not null,
userid int not null,
perfect tinyint(1) not null,
online_checksum char(32) not null
);
create index scores_map_md5_index
on scores (map_md5);
create index scores_score_index
on scores (score);
create index scores_pp_index
on scores (pp);
create index scores_mods_index
on scores (mods);
create index scores_status_index
on scores (status);
create index scores_mode_index
on scores (mode);
create index scores_play_time_index
on scores (play_time);
create index scores_userid_index
on scores (userid);
create index scores_online_checksum_index
on scores (online_checksum);
create index scores_fetch_leaderboard_generic_index
on scores (map_md5, status, mode);
create table startups
(
id int auto_increment
primary key,
ver_major tinyint not null,
ver_minor tinyint not null,
ver_micro tinyint not null,
datetime datetime not null
);
create table stats
(
id int auto_increment,
mode tinyint(1) not null,
tscore bigint unsigned default 0 not null,
rscore bigint unsigned default 0 not null,
pp int unsigned default 0 not null,
plays int unsigned default 0 not null,
playtime int unsigned default 0 not null,
acc float(6,3) default 0.000 not null,
max_combo int unsigned default 0 not null,
total_hits int unsigned default 0 not null,
replay_views int unsigned default 0 not null,
xh_count int unsigned default 0 not null,
x_count int unsigned default 0 not null,
sh_count int unsigned default 0 not null,
s_count int unsigned default 0 not null,
a_count int unsigned default 0 not null,
primary key (id, mode)
);
create index stats_mode_index
on stats (mode);
create index stats_pp_index
on stats (pp);
create index stats_tscore_index
on stats (tscore);
create index stats_rscore_index
on stats (rscore);
create table tourney_pool_maps
(
map_id int not null,
pool_id int not null,
mods int not null,
slot tinyint not null,
primary key (map_id, pool_id)
);
create index tourney_pool_maps_mods_slot_index
on tourney_pool_maps (mods, slot);
create index tourney_pool_maps_tourney_pools_id_fk
on tourney_pool_maps (pool_id);
create table tourney_pools
(
id int auto_increment
primary key,
name varchar(16) not null,
created_at datetime not null,
created_by int not null
);
create index tourney_pools_users_id_fk
on tourney_pools (created_by);
create table user_achievements
(
userid int not null,
achid int not null,
primary key (userid, achid)
);
create index user_achievements_achid_index
on user_achievements (achid);
create index user_achievements_userid_index
on user_achievements (userid);
create table users
(
id int auto_increment
primary key,
name varchar(32) charset utf8 not null,
safe_name varchar(32) charset utf8 not null,
email varchar(254) not null,
priv int default 1 not null,
pw_bcrypt char(60) not null,
country char(2) default 'xx' not null,
silence_end int default 0 not null,
donor_end int default 0 not null,
creation_time int default 0 not null,
latest_activity int default 0 not null,
clan_id int default 0 not null,
clan_priv tinyint(1) default 0 not null,
preferred_mode int default 0 not null,
play_style int default 0 not null,
custom_badge_name varchar(16) charset utf8 null,
custom_badge_icon varchar(64) null,
userpage_content varchar(2048) charset utf8 null,
api_key char(36) null,
constraint users_api_key_uindex
unique (api_key),
constraint users_email_uindex
unique (email),
constraint users_name_uindex
unique (name),
constraint users_safe_name_uindex
unique (safe_name)
);
create index users_priv_index
on users (priv);
create index users_clan_id_index
on users (clan_id);
create index users_clan_priv_index
on users (clan_priv);
create index users_country_index
on users (country);
insert into users (id, name, safe_name, priv, country, silence_end, email, pw_bcrypt, creation_time, latest_activity)
values (1, 'BanchoBot', 'banchobot', 1, 'ca', 0, 'bot@akatsuki.pw',
'_______________________my_cool_bcrypt_______________________', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
INSERT INTO stats (id, mode) VALUES (1, 0); # vn!std
INSERT INTO stats (id, mode) VALUES (1, 1); # vn!taiko
INSERT INTO stats (id, mode) VALUES (1, 2); # vn!catch
INSERT INTO stats (id, mode) VALUES (1, 3); # vn!mania
INSERT INTO stats (id, mode) VALUES (1, 4); # rx!std
INSERT INTO stats (id, mode) VALUES (1, 5); # rx!taiko
INSERT INTO stats (id, mode) VALUES (1, 6); # rx!catch
INSERT INTO stats (id, mode) VALUES (1, 8); # ap!std
# userid 2 is reserved for ppy in osu!, and the
# client will not allow users to pm this id.
# If you want this, simply remove these two lines.
alter table users auto_increment = 3;
alter table stats auto_increment = 3;
insert into channels (name, topic, read_priv, write_priv, auto_join)
values ('#osu', 'General discussion.', 1, 2, true),
('#announce', 'Exemplary performance and public announcements.', 1, 24576, true),
('#lobby', 'Multiplayer lobby discussion room.', 1, 2, false),
('#supporter', 'General discussion for supporters.', 48, 48, false),
('#staff', 'General discussion for staff members.', 28672, 28672, true),
('#admin', 'General discussion for administrators.', 24576, 24576, true),
('#dev', 'General discussion for developers.', 16384, 16384, true);
insert into achievements (id, file, name, `desc`, cond) values (1, 'osu-skill-pass-1', 'Rising Star', 'Can''t go forward without the first steps.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (2, 'osu-skill-pass-2', 'Constellation Prize', 'Definitely not a consolation prize. Now things start getting hard!', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (3, 'osu-skill-pass-3', 'Building Confidence', 'Oh, you''ve SO got this.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (4, 'osu-skill-pass-4', 'Insanity Approaches', 'You''re not twitching, you''re just ready.', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (5, 'osu-skill-pass-5', 'These Clarion Skies', 'Everything seems so clear now.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (6, 'osu-skill-pass-6', 'Above and Beyond', 'A cut above the rest.', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (7, 'osu-skill-pass-7', 'Supremacy', 'All marvel before your prowess.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (8, 'osu-skill-pass-8', 'Absolution', 'My god, you''re full of stars!', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (9, 'osu-skill-pass-9', 'Event Horizon', 'No force dares to pull you under.', '(score.mods & 1 == 0) and 9 <= score.sr < 10 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (10, 'osu-skill-pass-10', 'Phantasm', 'Fevered is your passion, extraordinary is your skill.', '(score.mods & 1 == 0) and 10 <= score.sr < 11 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (11, 'osu-skill-fc-1', 'Totality', 'All the notes. Every single one.', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (12, 'osu-skill-fc-2', 'Business As Usual', 'Two to go, please.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (13, 'osu-skill-fc-3', 'Building Steam', 'Hey, this isn''t so bad.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (14, 'osu-skill-fc-4', 'Moving Forward', 'Bet you feel good about that.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (15, 'osu-skill-fc-5', 'Paradigm Shift', 'Surprisingly difficult.', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (16, 'osu-skill-fc-6', 'Anguish Quelled', 'Don''t choke.', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (17, 'osu-skill-fc-7', 'Never Give Up', 'Excellence is its own reward.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (18, 'osu-skill-fc-8', 'Aberration', 'They said it couldn''t be done. They were wrong.', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (19, 'osu-skill-fc-9', 'Chosen', 'Reign among the Prometheans, where you belong.', 'score.perfect and 9 <= score.sr < 10 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (20, 'osu-skill-fc-10', 'Unfathomable', 'You have no equal.', 'score.perfect and 10 <= score.sr < 11 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (21, 'osu-combo-500', '500 Combo', '500 big ones! You''re moving up in the world!', '500 <= score.max_combo < 750 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (22, 'osu-combo-750', '750 Combo', '750 notes back to back? Woah.', '750 <= score.max_combo < 1000 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (23, 'osu-combo-1000', '1000 Combo', 'A thousand reasons why you rock at this game.', '1000 <= score.max_combo < 2000 and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (24, 'osu-combo-2000', '2000 Combo', 'Nothing can stop you now.', '2000 <= score.max_combo and mode_vn == 0');
insert into achievements (id, file, name, `desc`, cond) values (25, 'taiko-skill-pass-1', 'My First Don', 'Marching to the beat of your own drum. Literally.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (26, 'taiko-skill-pass-2', 'Katsu Katsu Katsu', 'Hora! Izuko!', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (27, 'taiko-skill-pass-3', 'Not Even Trying', 'Muzukashii? Not even.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (28, 'taiko-skill-pass-4', 'Face Your Demons', 'The first trials are now behind you, but are you a match for the Oni?', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (29, 'taiko-skill-pass-5', 'The Demon Within', 'No rest for the wicked.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (30, 'taiko-skill-pass-6', 'Drumbreaker', 'Too strong.', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (31, 'taiko-skill-pass-7', 'The Godfather', 'You are the Don of Dons.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (32, 'taiko-skill-pass-8', 'Rhythm Incarnate', 'Feel the beat. Become the beat.', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (33, 'taiko-skill-fc-1', 'Keeping Time', 'Don, then katsu. Don, then katsu..', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (34, 'taiko-skill-fc-2', 'To Your Own Beat', 'Straight and steady.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (35, 'taiko-skill-fc-3', 'Big Drums', 'Bigger scores to match.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (36, 'taiko-skill-fc-4', 'Adversity Overcome', 'Difficult? Not for you.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (37, 'taiko-skill-fc-5', 'Demonslayer', 'An Oni felled forevermore.', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (38, 'taiko-skill-fc-6', 'Rhythm''s Call', 'Heralding true skill.', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (39, 'taiko-skill-fc-7', 'Time Everlasting', 'Not a single beat escapes you.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (40, 'taiko-skill-fc-8', 'The Drummer''s Throne', 'Percussive brilliance befitting royalty alone.', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 1');
insert into achievements (id, file, name, `desc`, cond) values (41, 'fruits-skill-pass-1', 'A Slice Of Life', 'Hey, this fruit catching business isn''t bad.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (42, 'fruits-skill-pass-2', 'Dashing Ever Forward', 'Fast is how you do it.', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (43, 'fruits-skill-pass-3', 'Zesty Disposition', 'No scurvy for you, not with that much fruit.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (44, 'fruits-skill-pass-4', 'Hyperdash ON!', 'Time and distance is no obstacle to you.', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (45, 'fruits-skill-pass-5', 'It''s Raining Fruit', 'And you can catch them all.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (46, 'fruits-skill-pass-6', 'Fruit Ninja', 'Legendary techniques.', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (47, 'fruits-skill-pass-7', 'Dreamcatcher', 'No fruit, only dreams now.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (48, 'fruits-skill-pass-8', 'Lord of the Catch', 'Your kingdom kneels before you.', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (49, 'fruits-skill-fc-1', 'Sweet And Sour', 'Apples and oranges, literally.', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (50, 'fruits-skill-fc-2', 'Reaching The Core', 'The seeds of future success.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (51, 'fruits-skill-fc-3', 'Clean Platter', 'Clean only of failure. It is completely full, otherwise.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (52, 'fruits-skill-fc-4', 'Between The Rain', 'No umbrella needed.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (53, 'fruits-skill-fc-5', 'Addicted', 'That was an overdose?', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (54, 'fruits-skill-fc-6', 'Quickening', 'A dash above normal limits.', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (55, 'fruits-skill-fc-7', 'Supersonic', 'Faster than is reasonably necessary.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (56, 'fruits-skill-fc-8', 'Dashing Scarlet', 'Speed beyond mortal reckoning.', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 2');
insert into achievements (id, file, name, `desc`, cond) values (57, 'mania-skill-pass-1', 'First Steps', 'It isn''t 9-to-5, but 1-to-9. Keys, that is.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (58, 'mania-skill-pass-2', 'No Normal Player', 'Not anymore, at least.', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (59, 'mania-skill-pass-3', 'Impulse Drive', 'Not quite hyperspeed, but getting close.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (60, 'mania-skill-pass-4', 'Hyperspeed', 'Woah.', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (61, 'mania-skill-pass-5', 'Ever Onwards', 'Another challenge is just around the corner.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (62, 'mania-skill-pass-6', 'Another Surpassed', 'Is there no limit to your skills?', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (63, 'mania-skill-pass-7', 'Extra Credit', 'See me after class.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (64, 'mania-skill-pass-8', 'Maniac', 'There''s just no stopping you.', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (65, 'mania-skill-fc-1', 'Keystruck', 'The beginning of a new story', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (66, 'mania-skill-fc-2', 'Keying In', 'Finding your groove.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (67, 'mania-skill-fc-3', 'Hyperflow', 'You can *feel* the rhythm.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (68, 'mania-skill-fc-4', 'Breakthrough', 'Many skills mastered, rolled into one.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (69, 'mania-skill-fc-5', 'Everything Extra', 'Giving your all is giving everything you have.', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (70, 'mania-skill-fc-6', 'Level Breaker', 'Finesse beyond reason', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (71, 'mania-skill-fc-7', 'Step Up', 'A precipice rarely seen.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (72, 'mania-skill-fc-8', 'Behind The Veil', 'Supernatural!', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 3');
insert into achievements (id, file, name, `desc`, cond) values (73, 'all-intro-suddendeath', 'Finality', 'High stakes, no regrets.', 'score.mods == 32');
insert into achievements (id, file, name, `desc`, cond) values (74, 'all-intro-hidden', 'Blindsight', 'I can see just perfectly', 'score.mods & 8');
insert into achievements (id, file, name, `desc`, cond) values (75, 'all-intro-perfect', 'Perfectionist', 'Accept nothing but the best.', 'score.mods & 16384');
insert into achievements (id, file, name, `desc`, cond) values (76, 'all-intro-hardrock', 'Rock Around The Clock', "You can\'t stop the rock.", 'score.mods & 16');
insert into achievements (id, file, name, `desc`, cond) values (77, 'all-intro-doubletime', 'Time And A Half', "Having a right ol\' time. One and a half of them, almost.", 'score.mods & 64');
insert into achievements (id, file, name, `desc`, cond) values (78, 'all-intro-flashlight', 'Are You Afraid Of The Dark?', "Harder than it looks, probably because it\'s hard to look.", 'score.mods & 1024');
insert into achievements (id, file, name, `desc`, cond) values (79, 'all-intro-easy', 'Dial It Right Back', 'Sometimes you just want to take it easy.', 'score.mods & 2');
insert into achievements (id, file, name, `desc`, cond) values (80, 'all-intro-nofail', 'Risk Averse', 'Safety nets are fun!', 'score.mods & 1');
insert into achievements (id, file, name, `desc`, cond) values (81, 'all-intro-nightcore', 'Sweet Rave Party', 'Founded in the fine tradition of changing things that were just fine as they were.', 'score.mods & 512');
insert into achievements (id, file, name, `desc`, cond) values (82, 'all-intro-halftime', 'Slowboat', 'You got there. Eventually.', 'score.mods & 256');
insert into achievements (id, file, name, `desc`, cond) values (83, 'all-intro-spunout', 'Burned Out', 'One cannot always spin to win.', 'score.mods & 4096');

View File

@@ -1,209 +0,0 @@
-- 自定义谱面系统迁移
-- 创建自定义谱面表,与官方谱面不冲突
-- 自定义谱面集表
CREATE TABLE custom_mapsets (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
creator_id INT NOT NULL,
title VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
artist VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
source VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
tags TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
description TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
status ENUM('pending', 'approved', 'rejected', 'loved') DEFAULT 'pending',
upload_date DATETIME DEFAULT CURRENT_TIMESTAMP,
last_update DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
osz_filename VARCHAR(255) NOT NULL,
osz_hash CHAR(32) NOT NULL,
download_count INT DEFAULT 0,
favourite_count INT DEFAULT 0,
UNIQUE KEY idx_custom_mapsets_id (id),
KEY idx_custom_mapsets_creator (creator_id),
KEY idx_custom_mapsets_status (status),
KEY idx_custom_mapsets_upload_date (upload_date),
UNIQUE KEY idx_custom_mapsets_osz_hash (osz_hash)
);
-- 自定义谱面难度表
CREATE TABLE custom_maps (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
mapset_id BIGINT NOT NULL,
md5 CHAR(32) NOT NULL,
difficulty_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
filename VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
mode TINYINT DEFAULT 0 NOT NULL COMMENT '0=osu!, 1=taiko, 2=catch, 3=mania',
status ENUM('pending', 'approved', 'rejected', 'loved') DEFAULT 'pending',
-- osu!文件基本信息
audio_filename VARCHAR(255) DEFAULT '',
audio_lead_in INT DEFAULT 0,
preview_time INT DEFAULT -1,
countdown TINYINT DEFAULT 1,
sample_set VARCHAR(16) DEFAULT 'Normal',
stack_leniency DECIMAL(3,2) DEFAULT 0.70,
letterbox_in_breaks BOOLEAN DEFAULT FALSE,
story_fire_in_front BOOLEAN DEFAULT TRUE,
use_skin_sprites BOOLEAN DEFAULT FALSE,
always_show_playfield BOOLEAN DEFAULT FALSE,
overlay_position VARCHAR(16) DEFAULT 'NoChange',
skin_preference VARCHAR(255) DEFAULT '',
epilepsy_warning BOOLEAN DEFAULT FALSE,
countdown_offset INT DEFAULT 0,
special_style BOOLEAN DEFAULT FALSE,
widescreen_storyboard BOOLEAN DEFAULT FALSE,
samples_match_playback_rate BOOLEAN DEFAULT FALSE,
-- 编辑器信息
distance_spacing DECIMAL(6,3) DEFAULT 1.000,
beat_divisor TINYINT DEFAULT 4,
grid_size TINYINT DEFAULT 4,
timeline_zoom DECIMAL(6,3) DEFAULT 1.000,
-- 谱面元数据
title_unicode VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
artist_unicode VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
creator VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
version VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
source VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
tags TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
beatmap_id BIGINT DEFAULT 0,
beatmapset_id BIGINT DEFAULT 0,
-- 难度设定
hp_drain_rate DECIMAL(3,1) DEFAULT 5.0,
circle_size DECIMAL(3,1) DEFAULT 5.0,
overall_difficulty DECIMAL(3,1) DEFAULT 5.0,
approach_rate DECIMAL(3,1) DEFAULT 5.0,
slider_multiplier DECIMAL(6,3) DEFAULT 1.400,
slider_tick_rate DECIMAL(3,1) DEFAULT 1.0,
-- 计算得出的信息
total_length INT DEFAULT 0 COMMENT '总长度(秒)',
hit_length INT DEFAULT 0 COMMENT '击打长度(秒)',
max_combo INT DEFAULT 0,
bpm DECIMAL(8,3) DEFAULT 0.000,
star_rating DECIMAL(6,3) DEFAULT 0.000,
aim_difficulty DECIMAL(6,3) DEFAULT 0.000,
speed_difficulty DECIMAL(6,3) DEFAULT 0.000,
-- 统计信息
plays INT DEFAULT 0,
passes INT DEFAULT 0,
-- 时间戳
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY idx_custom_maps_id (id),
UNIQUE KEY idx_custom_maps_md5 (md5),
KEY idx_custom_maps_mapset (mapset_id),
KEY idx_custom_maps_mode (mode),
KEY idx_custom_maps_status (status),
KEY idx_custom_maps_creator (creator),
KEY idx_custom_maps_star_rating (star_rating),
KEY idx_custom_maps_plays (plays),
FOREIGN KEY (mapset_id) REFERENCES custom_mapsets(id) ON DELETE CASCADE
);
-- 自定义谱面书签表
CREATE TABLE custom_map_bookmarks (
user_id INT NOT NULL,
mapset_id BIGINT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, mapset_id),
FOREIGN KEY (mapset_id) REFERENCES custom_mapsets(id) ON DELETE CASCADE
);
-- 自定义谱面评分表
CREATE TABLE custom_map_ratings (
user_id INT NOT NULL,
map_id BIGINT NOT NULL,
rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 10),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, map_id),
FOREIGN KEY (map_id) REFERENCES custom_maps(id) ON DELETE CASCADE
);
-- 自定义谱面成绩表 (继承原scores表结构)
CREATE TABLE custom_scores (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
map_id BIGINT NOT NULL,
map_md5 CHAR(32) NOT NULL,
user_id INT NOT NULL,
score INT NOT NULL,
pp FLOAT(7,3) NOT NULL,
acc FLOAT(6,3) NOT NULL,
max_combo INT NOT NULL,
mods INT NOT NULL,
n300 INT NOT NULL,
n100 INT NOT NULL,
n50 INT NOT NULL,
nmiss INT NOT NULL,
ngeki INT NOT NULL,
nkatu INT NOT NULL,
grade VARCHAR(2) DEFAULT 'N' NOT NULL,
status TINYINT NOT NULL COMMENT '0=failed, 1=submitted, 2=best',
mode TINYINT NOT NULL,
play_time DATETIME NOT NULL,
time_elapsed INT NOT NULL,
client_flags INT NOT NULL,
perfect BOOLEAN NOT NULL,
online_checksum CHAR(32) NOT NULL,
KEY idx_custom_scores_map_id (map_id),
KEY idx_custom_scores_map_md5 (map_md5),
KEY idx_custom_scores_user_id (user_id),
KEY idx_custom_scores_score (score),
KEY idx_custom_scores_pp (pp),
KEY idx_custom_scores_mods (mods),
KEY idx_custom_scores_status (status),
KEY idx_custom_scores_mode (mode),
KEY idx_custom_scores_play_time (play_time),
KEY idx_custom_scores_online_checksum (online_checksum),
KEY idx_custom_scores_leaderboard (map_md5, status, mode),
FOREIGN KEY (map_id) REFERENCES custom_maps(id) ON DELETE CASCADE
);
-- 自定义谱面文件存储表 (用于存储.osu文件内容等)
CREATE TABLE custom_map_files (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
map_id BIGINT NOT NULL,
file_type ENUM('osu', 'audio', 'image', 'video', 'storyboard') NOT NULL,
filename VARCHAR(255) NOT NULL,
file_hash CHAR(32) NOT NULL,
file_size INT NOT NULL,
mime_type VARCHAR(100) DEFAULT '',
storage_path VARCHAR(500) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY idx_custom_map_files_id (id),
KEY idx_custom_map_files_map_id (map_id),
KEY idx_custom_map_files_type (file_type),
KEY idx_custom_map_files_hash (file_hash),
FOREIGN KEY (map_id) REFERENCES custom_maps(id) ON DELETE CASCADE
);
-- 为自定义谱面创建专门的ID生成器避免与官方ID冲突
-- 自定义谱面ID从1000000开始
ALTER TABLE custom_mapsets AUTO_INCREMENT = 3000000;
ALTER TABLE custom_maps AUTO_INCREMENT = 3000000;
-- 创建触发器来同步mapset信息到maps表
DELIMITER $$
CREATE TRIGGER update_custom_mapset_on_map_change
AFTER UPDATE ON custom_maps
FOR EACH ROW
BEGIN
IF NEW.status != OLD.status THEN
UPDATE custom_mapsets
SET last_update = CURRENT_TIMESTAMP
WHERE id = NEW.mapset_id;
END IF;
END$$
DELIMITER ;

View File

@@ -1,477 +0,0 @@
# This file contains any sql updates, along with the
# version they are required from. Touching this without
# at least reading utils/updater.py is certainly a bad idea :)
# v3.0.6
alter table users change name_safe safe_name varchar(32) not null;
alter table users drop key users_name_safe_uindex;
alter table users add constraint users_safe_name_uindex unique (safe_name);
alter table users change pw_hash pw_bcrypt char(60) not null;
insert into channels (name, topic, read_priv, write_priv, auto_join) values
('#supporter', 'General discussion for p2w gamers.', 48, 48, false),
('#staff', 'General discussion for the cool kids.', 28672, 28672, true),
('#admin', 'General discussion for the cool.', 24576, 24576, true),
('#dev', 'General discussion for the.', 16384, 16384, true);
# v3.0.8
alter table users modify safe_name varchar(32) charset utf8 not null;
alter table users modify name varchar(32) charset utf8 not null;
alter table mail modify msg varchar(2048) charset utf8 not null;
alter table logs modify msg varchar(2048) charset utf8 not null;
drop table if exists comments;
create table comments
(
id int auto_increment
primary key,
target_id int not null comment 'replay, map, or set id',
target_type enum('replay', 'map', 'song') not null,
userid int not null,
time int not null,
comment varchar(80) charset utf8 not null,
colour char(6) null comment 'rgb hex string'
);
# v3.0.9
alter table stats modify tscore_vn_std int unsigned default 0 not null;
alter table stats modify tscore_vn_taiko int unsigned default 0 not null;
alter table stats modify tscore_vn_catch int unsigned default 0 not null;
alter table stats modify tscore_vn_mania int unsigned default 0 not null;
alter table stats modify tscore_rx_std int unsigned default 0 not null;
alter table stats modify tscore_rx_taiko int unsigned default 0 not null;
alter table stats modify tscore_rx_catch int unsigned default 0 not null;
alter table stats modify tscore_ap_std int unsigned default 0 not null;
alter table stats modify rscore_vn_std int unsigned default 0 not null;
alter table stats modify rscore_vn_taiko int unsigned default 0 not null;
alter table stats modify rscore_vn_catch int unsigned default 0 not null;
alter table stats modify rscore_vn_mania int unsigned default 0 not null;
alter table stats modify rscore_rx_std int unsigned default 0 not null;
alter table stats modify rscore_rx_taiko int unsigned default 0 not null;
alter table stats modify rscore_rx_catch int unsigned default 0 not null;
alter table stats modify rscore_ap_std int unsigned default 0 not null;
alter table stats modify pp_vn_std smallint unsigned default 0 not null;
alter table stats modify pp_vn_taiko smallint unsigned default 0 not null;
alter table stats modify pp_vn_catch smallint unsigned default 0 not null;
alter table stats modify pp_vn_mania smallint unsigned default 0 not null;
alter table stats modify pp_rx_std smallint unsigned default 0 not null;
alter table stats modify pp_rx_taiko smallint unsigned default 0 not null;
alter table stats modify pp_rx_catch smallint unsigned default 0 not null;
alter table stats modify pp_ap_std smallint unsigned default 0 not null;
alter table stats modify plays_vn_std int unsigned default 0 not null;
alter table stats modify plays_vn_taiko int unsigned default 0 not null;
alter table stats modify plays_vn_catch int unsigned default 0 not null;
alter table stats modify plays_vn_mania int unsigned default 0 not null;
alter table stats modify plays_rx_std int unsigned default 0 not null;
alter table stats modify plays_rx_taiko int unsigned default 0 not null;
alter table stats modify plays_rx_catch int unsigned default 0 not null;
alter table stats modify plays_ap_std int unsigned default 0 not null;
alter table stats modify playtime_vn_std int unsigned default 0 not null;
alter table stats modify playtime_vn_taiko int unsigned default 0 not null;
alter table stats modify playtime_vn_catch int unsigned default 0 not null;
alter table stats modify playtime_vn_mania int unsigned default 0 not null;
alter table stats modify playtime_rx_std int unsigned default 0 not null;
alter table stats modify playtime_rx_taiko int unsigned default 0 not null;
alter table stats modify playtime_rx_catch int unsigned default 0 not null;
alter table stats modify playtime_ap_std int unsigned default 0 not null;
alter table stats modify maxcombo_vn_std int unsigned default 0 not null;
alter table stats modify maxcombo_vn_taiko int unsigned default 0 not null;
alter table stats modify maxcombo_vn_catch int unsigned default 0 not null;
alter table stats modify maxcombo_vn_mania int unsigned default 0 not null;
alter table stats modify maxcombo_rx_std int unsigned default 0 not null;
alter table stats modify maxcombo_rx_taiko int unsigned default 0 not null;
alter table stats modify maxcombo_rx_catch int unsigned default 0 not null;
alter table stats modify maxcombo_ap_std int unsigned default 0 not null;
# v3.0.10
update channels set write_priv = 24576 where name = '#announce';
# v3.1.0
alter table maps modify bpm float(12,2) default 0.00 not null;
alter table stats modify tscore_vn_std bigint unsigned default 0 not null;
alter table stats modify tscore_vn_taiko bigint unsigned default 0 not null;
alter table stats modify tscore_vn_catch bigint unsigned default 0 not null;
alter table stats modify tscore_vn_mania bigint unsigned default 0 not null;
alter table stats modify tscore_rx_std bigint unsigned default 0 not null;
alter table stats modify tscore_rx_taiko bigint unsigned default 0 not null;
alter table stats modify tscore_rx_catch bigint unsigned default 0 not null;
alter table stats modify tscore_ap_std bigint unsigned default 0 not null;
alter table stats modify rscore_vn_std bigint unsigned default 0 not null;
alter table stats modify rscore_vn_taiko bigint unsigned default 0 not null;
alter table stats modify rscore_vn_catch bigint unsigned default 0 not null;
alter table stats modify rscore_vn_mania bigint unsigned default 0 not null;
alter table stats modify rscore_rx_std bigint unsigned default 0 not null;
alter table stats modify rscore_rx_taiko bigint unsigned default 0 not null;
alter table stats modify rscore_rx_catch bigint unsigned default 0 not null;
alter table stats modify rscore_ap_std bigint unsigned default 0 not null;
alter table stats modify pp_vn_std int unsigned default 0 not null;
alter table stats modify pp_vn_taiko int unsigned default 0 not null;
alter table stats modify pp_vn_catch int unsigned default 0 not null;
alter table stats modify pp_vn_mania int unsigned default 0 not null;
alter table stats modify pp_rx_std int unsigned default 0 not null;
alter table stats modify pp_rx_taiko int unsigned default 0 not null;
alter table stats modify pp_rx_catch int unsigned default 0 not null;
alter table stats modify pp_ap_std int unsigned default 0 not null;
# v3.1.2
create table clans
(
id int auto_increment
primary key,
name varchar(16) not null,
tag varchar(6) not null,
owner int not null,
created_at datetime not null,
constraint clans_name_uindex
unique (name),
constraint clans_owner_uindex
unique (owner),
constraint clans_tag_uindex
unique (tag)
);
alter table users add clan_id int default 0 not null;
alter table users add clan_rank tinyint(1) default 0 not null;
create table achievements
(
id int auto_increment
primary key,
file varchar(128) not null,
name varchar(128) not null,
`desc` varchar(256) not null,
cond varchar(64) not null,
mode tinyint(1) not null,
constraint achievements_desc_uindex
unique (`desc`),
constraint achievements_file_uindex
unique (file),
constraint achievements_name_uindex
unique (name)
);
create table user_achievements
(
userid int not null,
achid int not null,
primary key (userid, achid)
);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (1, 'osu-skill-pass-1', 'Rising Star', 'Can''t go forward without the first steps.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (2, 'osu-skill-pass-2', 'Constellation Prize', 'Definitely not a consolation prize. Now things start getting hard!', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (3, 'osu-skill-pass-3', 'Building Confidence', 'Oh, you''ve SO got this.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (4, 'osu-skill-pass-4', 'Insanity Approaches', 'You''re not twitching, you''re just ready.', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (5, 'osu-skill-pass-5', 'These Clarion Skies', 'Everything seems so clear now.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (6, 'osu-skill-pass-6', 'Above and Beyond', 'A cut above the rest.', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (7, 'osu-skill-pass-7', 'Supremacy', 'All marvel before your prowess.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (8, 'osu-skill-pass-8', 'Absolution', 'My god, you''re full of stars!', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (9, 'osu-skill-pass-9', 'Event Horizon', 'No force dares to pull you under.', '(score.mods & 259 == 0) and 10 >= score.sr > 9', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (10, 'osu-skill-pass-10', 'Phantasm', 'Fevered is your passion, extraordinary is your skill.', '(score.mods & 259 == 0) and 11 >= score.sr > 10', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (11, 'osu-skill-fc-1', 'Totality', 'All the notes. Every single one.', 'score.perfect and 2 >= score.sr > 1', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (12, 'osu-skill-fc-2', 'Business As Usual', 'Two to go, please.', 'score.perfect and 3 >= score.sr > 2', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (13, 'osu-skill-fc-3', 'Building Steam', 'Hey, this isn''t so bad.', 'score.perfect and 4 >= score.sr > 3', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (14, 'osu-skill-fc-4', 'Moving Forward', 'Bet you feel good about that.', 'score.perfect and 5 >= score.sr > 4', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (15, 'osu-skill-fc-5', 'Paradigm Shift', 'Surprisingly difficult.', 'score.perfect and 6 >= score.sr > 5', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (16, 'osu-skill-fc-6', 'Anguish Quelled', 'Don''t choke.', 'score.perfect and 7 >= score.sr > 6', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (17, 'osu-skill-fc-7', 'Never Give Up', 'Excellence is its own reward.', 'score.perfect and 8 >= score.sr > 7', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (18, 'osu-skill-fc-8', 'Aberration', 'They said it couldn''t be done. They were wrong.', 'score.perfect and 9 >= score.sr > 8', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (19, 'osu-skill-fc-9', 'Chosen', 'Reign among the Prometheans, where you belong.', 'score.perfect and 10 >= score.sr > 9', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (20, 'osu-skill-fc-10', 'Unfathomable', 'You have no equal.', 'score.perfect and 11 >= score.sr > 10', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (21, 'osu-combo-500', '500 Combo', '500 big ones! You''re moving up in the world!', '750 >= score.max_combo > 500', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (22, 'osu-combo-750', '750 Combo', '750 notes back to back? Woah.', '1000 >= score.max_combo > 750', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (23, 'osu-combo-1000', '1000 Combo', 'A thousand reasons why you rock at this game.', '2000 >= score.max_combo > 1000', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (24, 'osu-combo-2000', '2000 Combo', 'Nothing can stop you now.', 'score.max_combo >= 2000', 0);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (25, 'taiko-skill-pass-1', 'My First Don', 'Marching to the beat of your own drum. Literally.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (26, 'taiko-skill-pass-2', 'Katsu Katsu Katsu', 'Hora! Izuko!', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (27, 'taiko-skill-pass-3', 'Not Even Trying', 'Muzukashii? Not even.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (28, 'taiko-skill-pass-4', 'Face Your Demons', 'The first trials are now behind you, but are you a match for the Oni?', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (29, 'taiko-skill-pass-5', 'The Demon Within', 'No rest for the wicked.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (30, 'taiko-skill-pass-6', 'Drumbreaker', 'Too strong.', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (31, 'taiko-skill-pass-7', 'The Godfather', 'You are the Don of Dons.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (32, 'taiko-skill-pass-8', 'Rhythm Incarnate', 'Feel the beat. Become the beat.', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (33, 'taiko-skill-fc-1', 'Keeping Time', 'Don, then katsu. Don, then katsu..', 'score.perfect and 2 >= score.sr > 1', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (34, 'taiko-skill-fc-2', 'To Your Own Beat', 'Straight and steady.', 'score.perfect and 3 >= score.sr > 2', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (35, 'taiko-skill-fc-3', 'Big Drums', 'Bigger scores to match.', 'score.perfect and 4 >= score.sr > 3', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (36, 'taiko-skill-fc-4', 'Adversity Overcome', 'Difficult? Not for you.', 'score.perfect and 5 >= score.sr > 4', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (37, 'taiko-skill-fc-5', 'Demonslayer', 'An Oni felled forevermore.', 'score.perfect and 6 >= score.sr > 5', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (38, 'taiko-skill-fc-6', 'Rhythm''s Call', 'Heralding true skill.', 'score.perfect and 7 >= score.sr > 6', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (39, 'taiko-skill-fc-7', 'Time Everlasting', 'Not a single beat escapes you.', 'score.perfect and 8 >= score.sr > 7', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (40, 'taiko-skill-fc-8', 'The Drummer''s Throne', 'Percussive brilliance befitting royalty alone.', 'score.perfect and 9 >= score.sr > 8', 1);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (41, 'fruits-skill-pass-1', 'A Slice Of Life', 'Hey, this fruit catching business isn''t bad.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (42, 'fruits-skill-pass-2', 'Dashing Ever Forward', 'Fast is how you do it.', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (43, 'fruits-skill-pass-3', 'Zesty Disposition', 'No scurvy for you, not with that much fruit.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (44, 'fruits-skill-pass-4', 'Hyperdash ON!', 'Time and distance is no obstacle to you.', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (45, 'fruits-skill-pass-5', 'It''s Raining Fruit', 'And you can catch them all.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (46, 'fruits-skill-pass-6', 'Fruit Ninja', 'Legendary techniques.', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (47, 'fruits-skill-pass-7', 'Dreamcatcher', 'No fruit, only dreams now.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (48, 'fruits-skill-pass-8', 'Lord of the Catch', 'Your kingdom kneels before you.', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (49, 'fruits-skill-fc-1', 'Sweet And Sour', 'Apples and oranges, literally.', 'score.perfect and 2 >= score.sr > 1', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (50, 'fruits-skill-fc-2', 'Reaching The Core', 'The seeds of future success.', 'score.perfect and 3 >= score.sr > 2', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (51, 'fruits-skill-fc-3', 'Clean Platter', 'Clean only of failure. It is completely full, otherwise.', 'score.perfect and 4 >= score.sr > 3', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (52, 'fruits-skill-fc-4', 'Between The Rain', 'No umbrella needed.', 'score.perfect and 5 >= score.sr > 4', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (53, 'fruits-skill-fc-5', 'Addicted', 'That was an overdose?', 'score.perfect and 6 >= score.sr > 5', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (54, 'fruits-skill-fc-6', 'Quickening', 'A dash above normal limits.', 'score.perfect and 7 >= score.sr > 6', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (55, 'fruits-skill-fc-7', 'Supersonic', 'Faster than is reasonably necessary.', 'score.perfect and 8 >= score.sr > 7', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (56, 'fruits-skill-fc-8', 'Dashing Scarlet', 'Speed beyond mortal reckoning.', 'score.perfect and 9 >= score.sr > 8', 2);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (57, 'mania-skill-pass-1', 'First Steps', 'It isn''t 9-to-5, but 1-to-9. Keys, that is.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (58, 'mania-skill-pass-2', 'No Normal Player', 'Not anymore, at least.', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (59, 'mania-skill-pass-3', 'Impulse Drive', 'Not quite hyperspeed, but getting close.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (60, 'mania-skill-pass-4', 'Hyperspeed', 'Woah.', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (61, 'mania-skill-pass-5', 'Ever Onwards', 'Another challenge is just around the corner.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (62, 'mania-skill-pass-6', 'Another Surpassed', 'Is there no limit to your skills?', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (63, 'mania-skill-pass-7', 'Extra Credit', 'See me after class.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (64, 'mania-skill-pass-8', 'Maniac', 'There''s just no stopping you.', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (65, 'mania-skill-fc-1', 'Keystruck', 'The beginning of a new story', 'score.perfect and (score.mods & 259 == 0) and 2 >= score.sr > 1', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (66, 'mania-skill-fc-2', 'Keying In', 'Finding your groove.', 'score.perfect and 3 >= score.sr > 2', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (67, 'mania-skill-fc-3', 'Hyperflow', 'You can *feel* the rhythm.', 'score.perfect and 4 >= score.sr > 3', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (68, 'mania-skill-fc-4', 'Breakthrough', 'Many skills mastered, rolled into one.', 'score.perfect and 5 >= score.sr > 4', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (69, 'mania-skill-fc-5', 'Everything Extra', 'Giving your all is giving everything you have.', 'score.perfect and 6 >= score.sr > 5', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (70, 'mania-skill-fc-6', 'Level Breaker', 'Finesse beyond reason', 'score.perfect and 7 >= score.sr > 6', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (71, 'mania-skill-fc-7', 'Step Up', 'A precipice rarely seen.', 'score.perfect and 8 >= score.sr > 7', 3);
insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (72, 'mania-skill-fc-8', 'Behind The Veil', 'Supernatural!', 'score.perfect and 9 >= score.sr > 8', 3);
# v3.1.3
alter table clans modify name varchar(16) charset utf8 not null;
alter table clans modify tag varchar(6) charset utf8 not null;
alter table achievements modify name varchar(128) charset utf8 not null;
alter table achievements modify `desc` varchar(256) charset utf8 not null;
alter table maps modify artist varchar(128) charset utf8 not null;
alter table maps modify title varchar(128) charset utf8 not null;
alter table maps modify version varchar(128) charset utf8 not null;
alter table maps modify creator varchar(19) charset utf8 not null comment 'not 100%% certain on len';
alter table tourney_pools drop foreign key tourney_pools_users_id_fk;
alter table tourney_pool_maps drop foreign key tourney_pool_maps_tourney_pools_id_fk;
alter table stats drop foreign key stats_users_id_fk;
alter table ratings drop foreign key ratings_maps_md5_fk;
alter table ratings drop foreign key ratings_users_id_fk;
alter table logs modify `from` int not null comment 'both from and to are playerids';
# v3.1.9
alter table scores_rx modify id bigint(20) unsigned auto_increment;
update scores_rx set id = id + (6148914691236517205 - 1);
select @max_rx := MAX(id) + 1 from scores_rx;
set @s = CONCAT('alter table scores_rx auto_increment = ', @max_rx);
prepare stmt from @s;
execute stmt;
deallocate PREPARE stmt;
alter table scores_ap modify id bigint(20) unsigned auto_increment;
update scores_ap set id = id + (12297829382473034410 - 1);
select @max_ap := MAX(id) + 1 from scores_ap;
set @s = CONCAT('alter table scores_ap auto_increment = ', @max_ap);
prepare stmt from @s;
execute stmt;
deallocate PREPARE stmt;
alter table performance_reports modify scoreid bigint(20) unsigned auto_increment;
# v3.2.0
create table map_requests
(
id int auto_increment
primary key,
map_id int not null,
player_id int not null,
datetime datetime not null,
active tinyint(1) not null
);
# v3.2.1
update scores_rx set id = id - 3074457345618258603;
update scores_ap set id = id - 6148914691236517206;
# v3.2.2
alter table maps add max_combo int not null after total_length;
alter table users change clan_rank clan_priv tinyint(1) default 0 not null;
# v3.2.3
alter table users add api_key char(36) default NULL null;
create unique index users_api_key_uindex on users (api_key);
# v3.2.4
update achievements set file = replace(file, 'ctb', 'fruits') where mode = 2;
# v3.2.5
update achievements set cond = '(score.mods & 1 == 0) and 1 <= score.sr < 2' where file in ('osu-skill-pass-1', 'taiko-skill-pass-1', 'fruits-skill-pass-1', 'mania-skill-pass-1');
update achievements set cond = '(score.mods & 1 == 0) and 2 <= score.sr < 3' where file in ('osu-skill-pass-2', 'taiko-skill-pass-2', 'fruits-skill-pass-2', 'mania-skill-pass-2');
update achievements set cond = '(score.mods & 1 == 0) and 3 <= score.sr < 4' where file in ('osu-skill-pass-3', 'taiko-skill-pass-3', 'fruits-skill-pass-3', 'mania-skill-pass-3');
update achievements set cond = '(score.mods & 1 == 0) and 4 <= score.sr < 5' where file in ('osu-skill-pass-4', 'taiko-skill-pass-4', 'fruits-skill-pass-4', 'mania-skill-pass-4');
update achievements set cond = '(score.mods & 1 == 0) and 5 <= score.sr < 6' where file in ('osu-skill-pass-5', 'taiko-skill-pass-5', 'fruits-skill-pass-5', 'mania-skill-pass-5');
update achievements set cond = '(score.mods & 1 == 0) and 6 <= score.sr < 7' where file in ('osu-skill-pass-6', 'taiko-skill-pass-6', 'fruits-skill-pass-6', 'mania-skill-pass-6');
update achievements set cond = '(score.mods & 1 == 0) and 7 <= score.sr < 8' where file in ('osu-skill-pass-7', 'taiko-skill-pass-7', 'fruits-skill-pass-7', 'mania-skill-pass-7');
update achievements set cond = '(score.mods & 1 == 0) and 8 <= score.sr < 9' where file in ('osu-skill-pass-8', 'taiko-skill-pass-8', 'fruits-skill-pass-8', 'mania-skill-pass-8');
update achievements set cond = '(score.mods & 1 == 0) and 9 <= score.sr < 10' where file = 'osu-skill-pass-9';
update achievements set cond = '(score.mods & 1 == 0) and 10 <= score.sr < 11' where file = 'osu-skill-pass-10';
update achievements set cond = 'score.perfect and 1 <= score.sr < 2' where file in ('osu-skill-fc-1', 'taiko-skill-fc-1', 'fruits-skill-fc-1', 'mania-skill-fc-1');
update achievements set cond = 'score.perfect and 2 <= score.sr < 3' where file in ('osu-skill-fc-2', 'taiko-skill-fc-2', 'fruits-skill-fc-2', 'mania-skill-fc-2');
update achievements set cond = 'score.perfect and 3 <= score.sr < 4' where file in ('osu-skill-fc-3', 'taiko-skill-fc-3', 'fruits-skill-fc-3', 'mania-skill-fc-3');
update achievements set cond = 'score.perfect and 4 <= score.sr < 5' where file in ('osu-skill-fc-4', 'taiko-skill-fc-4', 'fruits-skill-fc-4', 'mania-skill-fc-4');
update achievements set cond = 'score.perfect and 5 <= score.sr < 6' where file in ('osu-skill-fc-5', 'taiko-skill-fc-5', 'fruits-skill-fc-5', 'mania-skill-fc-5');
update achievements set cond = 'score.perfect and 6 <= score.sr < 7' where file in ('osu-skill-fc-6', 'taiko-skill-fc-6', 'fruits-skill-fc-6', 'mania-skill-fc-6');
update achievements set cond = 'score.perfect and 7 <= score.sr < 8' where file in ('osu-skill-fc-7', 'taiko-skill-fc-7', 'fruits-skill-fc-7', 'mania-skill-fc-7');
update achievements set cond = 'score.perfect and 8 <= score.sr < 9' where file in ('osu-skill-fc-8', 'taiko-skill-fc-8', 'fruits-skill-fc-8', 'mania-skill-fc-8');
update achievements set cond = 'score.perfect and 9 <= score.sr < 10' where file = 'osu-skill-fc-9';
update achievements set cond = 'score.perfect and 10 <= score.sr < 11' where file = 'osu-skill-fc-10';
update achievements set cond = '500 <= score.max_combo < 750' where file = 'osu-combo-500';
update achievements set cond = '750 <= score.max_combo < 1000' where file = 'osu-combo-750';
update achievements set cond = '1000 <= score.max_combo < 2000' where file = 'osu-combo-1000';
update achievements set cond = '2000 <= score.max_combo' where file = 'osu-combo-2000';
# v3.2.6
alter table stats change maxcombo_vn_std max_combo_vn_std int unsigned default 0 not null;
alter table stats change maxcombo_vn_taiko max_combo_vn_taiko int unsigned default 0 not null;
alter table stats change maxcombo_vn_catch max_combo_vn_catch int unsigned default 0 not null;
alter table stats change maxcombo_vn_mania max_combo_vn_mania int unsigned default 0 not null;
alter table stats change maxcombo_rx_std max_combo_rx_std int unsigned default 0 not null;
alter table stats change maxcombo_rx_taiko max_combo_rx_taiko int unsigned default 0 not null;
alter table stats change maxcombo_rx_catch max_combo_rx_catch int unsigned default 0 not null;
alter table stats change maxcombo_ap_std max_combo_ap_std int unsigned default 0 not null;
# v3.2.7
drop table if exists user_hashes;
# v3.3.0
rename table friendships to relationships;
alter table relationships add type enum('friend', 'block') not null;
# v3.3.1
create table ingame_logins
(
id int auto_increment
primary key,
userid int not null,
ip varchar(45) not null comment 'maxlen for ipv6',
osu_ver date not null,
osu_stream varchar(11) not null,
datetime datetime not null
);
# v3.3.7
update achievements set cond = CONCAT(cond, ' and mode_vn == 0') where mode = 0;
update achievements set cond = CONCAT(cond, ' and mode_vn == 1') where mode = 1;
update achievements set cond = CONCAT(cond, ' and mode_vn == 2') where mode = 2;
update achievements set cond = CONCAT(cond, ' and mode_vn == 3') where mode = 3;
alter table achievements drop column mode;
# v3.3.8
create table mapsets
(
server enum('osu!', 'gulag') default 'osu!' not null,
id int not null,
last_osuapi_check datetime default CURRENT_TIMESTAMP not null,
primary key (server, id),
constraint nmapsets_id_uindex
unique (id)
);
# v3.4.1
alter table maps add filename varchar(256) charset utf8 not null after creator;
# v3.5.2
alter table scores_vn add online_checksum char(32) not null;
alter table scores_rx add online_checksum char(32) not null;
alter table scores_ap add online_checksum char(32) not null;
# v4.1.1
alter table stats add total_hits int unsigned default 0 not null after max_combo;
# v4.1.2
alter table stats add replay_views int unsigned default 0 not null after total_hits;
# v4.1.3
alter table users add preferred_mode int default 0 not null after latest_activity;
alter table users add play_style int default 0 not null after preferred_mode;
alter table users add custom_badge_name varchar(16) charset utf8 null after play_style;
alter table users add custom_badge_icon varchar(64) null after custom_badge_name;
alter table users add userpage_content varchar(2048) charset utf8 null after custom_badge_icon;
# v4.2.0
# please refer to tools/migrate_v420 for further v4.2.0 migrations
update stats set mode = 8 where mode = 7;
# v4.3.1
alter table maps change server server enum('osu!', 'private') default 'osu!' not null;
alter table mapsets change server server enum('osu!', 'private') default 'osu!' not null;
# v4.4.2
insert into achievements (id, file, name, `desc`, cond) values (73, 'all-intro-suddendeath', 'Finality', 'High stakes, no regrets.', 'score.mods == 32');
insert into achievements (id, file, name, `desc`, cond) values (74, 'all-intro-hidden', 'Blindsight', 'I can see just perfectly', 'score.mods & 8');
insert into achievements (id, file, name, `desc`, cond) values (75, 'all-intro-perfect', 'Perfectionist', 'Accept nothing but the best.', 'score.mods & 16384');
insert into achievements (id, file, name, `desc`, cond) values (76, 'all-intro-hardrock', 'Rock Around The Clock', "You can\'t stop the rock.", 'score.mods & 16');
insert into achievements (id, file, name, `desc`, cond) values (77, 'all-intro-doubletime', 'Time And A Half', "Having a right ol\' time. One and a half of them, almost.", 'score.mods & 64');
insert into achievements (id, file, name, `desc`, cond) values (78, 'all-intro-flashlight', 'Are You Afraid Of The Dark?', "Harder than it looks, probably because it\'s hard to look.", 'score.mods & 1024');
insert into achievements (id, file, name, `desc`, cond) values (79, 'all-intro-easy', 'Dial It Right Back', 'Sometimes you just want to take it easy.', 'score.mods & 2');
insert into achievements (id, file, name, `desc`, cond) values (80, 'all-intro-nofail', 'Risk Averse', 'Safety nets are fun!', 'score.mods & 1');
insert into achievements (id, file, name, `desc`, cond) values (81, 'all-intro-nightcore', 'Sweet Rave Party', 'Founded in the fine tradition of changing things that were just fine as they were.', 'score.mods & 512');
insert into achievements (id, file, name, `desc`, cond) values (82, 'all-intro-halftime', 'Slowboat', 'You got there. Eventually.', 'score.mods & 256');
insert into achievements (id, file, name, `desc`, cond) values (83, 'all-intro-spunout', 'Burned Out', 'One cannot always spin to win.', 'score.mods & 4096');
# v4.4.3
alter table favourites add created_at int default 0 not null;
# v4.7.1
lock tables maps write;
alter table maps drop primary key;
alter table maps add primary key (id);
alter table maps modify column server enum('osu!', 'private') not null default 'osu!' after id;
unlock tables;
# v5.0.1
create index channels_auto_join_index
on channels (auto_join);
create index maps_set_id_index
on maps (set_id);
create index maps_status_index
on maps (status);
create index maps_filename_index
on maps (filename);
create index maps_plays_index
on maps (plays);
create index maps_mode_index
on maps (mode);
create index maps_frozen_index
on maps (frozen);
create index scores_map_md5_index
on scores (map_md5);
create index scores_score_index
on scores (score);
create index scores_pp_index
on scores (pp);
create index scores_mods_index
on scores (mods);
create index scores_status_index
on scores (status);
create index scores_mode_index
on scores (mode);
create index scores_play_time_index
on scores (play_time);
create index scores_userid_index
on scores (userid);
create index scores_online_checksum_index
on scores (online_checksum);
create index stats_mode_index
on stats (mode);
create index stats_pp_index
on stats (pp);
create index stats_tscore_index
on stats (tscore);
create index stats_rscore_index
on stats (rscore);
create index tourney_pool_maps_mods_slot_index
on tourney_pool_maps (mods, slot);
create index user_achievements_achid_index
on user_achievements (achid);
create index user_achievements_userid_index
on user_achievements (userid);
create index users_priv_index
on users (priv);
create index users_clan_id_index
on users (clan_id);
create index users_clan_priv_index
on users (clan_priv);
create index users_country_index
on users (country);
# v5.2.2
create index scores_fetch_leaderboard_generic_index
on scores (map_md5, status, mode);

View File

@@ -1,337 +0,0 @@
-- Lazer API 数据同步脚本
-- 从现有的 bancho.py 表结构同步数据到新的 lazer 专用表
-- 执行此脚本前请确保已执行 add_missing_fields.sql
-- ============================================
-- 同步用户基本资料数据
-- ============================================
-- 同步用户扩展资料
INSERT INTO lazer_user_profiles (
user_id,
is_active,
is_bot,
is_deleted,
is_online,
is_supporter,
is_restricted,
session_verified,
has_supported,
pm_friends_only,
default_group,
last_visit,
join_date,
profile_colour,
profile_hue,
avatar_url,
cover_url,
discord,
twitter,
website,
title,
title_url,
interests,
location,
occupation,
playmode,
support_level,
max_blocks,
max_friends,
post_count,
page_html,
page_raw
)
SELECT
u.id as user_id,
-- 基本状态字段 (使用默认值,因为原表没有这些字段)
1 as is_active,
CASE WHEN u.name = 'BanchoBot' THEN 1 ELSE 0 END as is_bot,
0 as is_deleted,
1 as is_online,
CASE WHEN u.donor_end > UNIX_TIMESTAMP() THEN 1 ELSE 0 END as is_supporter,
CASE WHEN (u.priv & 1) = 0 THEN 1 ELSE 0 END as is_restricted,
0 as session_verified,
CASE WHEN u.donor_end > 0 THEN 1 ELSE 0 END as has_supported,
0 as pm_friends_only,
-- 基本资料字段
'default' as default_group,
CASE WHEN u.latest_activity > 0 THEN FROM_UNIXTIME(u.latest_activity) ELSE NULL END as last_visit,
CASE WHEN u.creation_time > 0 THEN FROM_UNIXTIME(u.creation_time) ELSE NULL END as join_date,
NULL as profile_colour,
NULL as profile_hue,
-- 社交媒体和个人资料字段 (使用默认值)
CONCAT('https://a.ppy.sh/', u.id) as avatar_url,
CONCAT('https://assets.ppy.sh/user-profile-covers/banners/', u.id, '.jpg') as cover_url,
NULL as discord,
NULL as twitter,
NULL as website,
u.custom_badge_name as title,
NULL as title_url,
NULL as interests,
CASE WHEN u.country != 'xx' THEN u.country ELSE NULL END as location,
NULL as occupation,
-- 游戏相关字段
CASE u.preferred_mode
WHEN 0 THEN 'osu'
WHEN 1 THEN 'taiko'
WHEN 2 THEN 'fruits'
WHEN 3 THEN 'mania'
ELSE 'osu'
END as playmode,
CASE WHEN u.donor_end > UNIX_TIMESTAMP() THEN 1 ELSE 0 END as support_level,
100 as max_blocks,
500 as max_friends,
0 as post_count,
-- 页面内容
u.userpage_content as page_html,
u.userpage_content as page_raw
FROM users u
ON DUPLICATE KEY UPDATE
last_visit = VALUES(last_visit),
join_date = VALUES(join_date),
is_supporter = VALUES(is_supporter),
is_restricted = VALUES(is_restricted),
has_supported = VALUES(has_supported),
title = VALUES(title),
location = VALUES(location),
playmode = VALUES(playmode),
support_level = VALUES(support_level),
page_html = VALUES(page_html),
page_raw = VALUES(page_raw);
-- 同步用户国家信息
INSERT INTO lazer_user_countries (
user_id,
code,
name
)
SELECT
u.id as user_id,
UPPER(u.country) as code,
CASE UPPER(u.country)
WHEN 'CN' THEN 'China'
WHEN 'US' THEN 'United States'
WHEN 'JP' THEN 'Japan'
WHEN 'KR' THEN 'South Korea'
WHEN 'CA' THEN 'Canada'
WHEN 'GB' THEN 'United Kingdom'
WHEN 'DE' THEN 'Germany'
WHEN 'FR' THEN 'France'
WHEN 'AU' THEN 'Australia'
WHEN 'RU' THEN 'Russia'
ELSE 'Unknown'
END as name
FROM users u
WHERE u.country IS NOT NULL AND u.country != 'xx'
ON DUPLICATE KEY UPDATE
code = VALUES(code),
name = VALUES(name);
-- 同步用户 Kudosu (使用默认值)
INSERT INTO lazer_user_kudosu (
user_id,
available,
total
)
SELECT
u.id as user_id,
0 as available,
0 as total
FROM users u
ON DUPLICATE KEY UPDATE
available = VALUES(available),
total = VALUES(total);
-- 同步用户统计计数 (使用默认值)
INSERT INTO lazer_user_counts (
user_id,
beatmap_playcounts_count,
comments_count,
favourite_beatmapset_count,
follower_count,
graveyard_beatmapset_count,
guest_beatmapset_count,
loved_beatmapset_count,
mapping_follower_count,
nominated_beatmapset_count,
pending_beatmapset_count,
ranked_beatmapset_count,
ranked_and_approved_beatmapset_count,
unranked_beatmapset_count,
scores_best_count,
scores_first_count,
scores_pinned_count,
scores_recent_count
)
SELECT
u.id as user_id,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
FROM users u
ON DUPLICATE KEY UPDATE
user_id = VALUES(user_id);
-- ============================================
-- 同步游戏统计数据
-- ============================================
-- 从 stats 表同步用户统计数据到 lazer_user_statistics
INSERT INTO lazer_user_statistics (
user_id,
mode,
count_100,
count_300,
count_50,
count_miss,
level_current,
level_progress,
global_rank,
country_rank,
pp,
ranked_score,
hit_accuracy,
total_score,
total_hits,
maximum_combo,
play_count,
play_time,
replays_watched_by_others,
is_ranked,
grade_ss,
grade_ssh,
grade_s,
grade_sh,
grade_a
)
SELECT
s.id as user_id,
CASE s.mode
WHEN 0 THEN 'osu'
WHEN 1 THEN 'taiko'
WHEN 2 THEN 'fruits'
WHEN 3 THEN 'mania'
ELSE 'osu'
END as mode,
-- 基本命中统计
s.n100 as count_100,
s.n300 as count_300,
s.n50 as count_50,
s.nmiss as count_miss,
-- 等级信息
1 as level_current,
0 as level_progress,
-- 排名信息
NULL as global_rank,
NULL as country_rank,
-- PP 和分数
s.pp as pp,
s.rscore as ranked_score,
CASE WHEN (s.n300 + s.n100 + s.n50 + s.nmiss) > 0
THEN ROUND((s.n300 * 300 + s.n100 * 100 + s.n50 * 50) / ((s.n300 + s.n100 + s.n50 + s.nmiss) * 300) * 100, 2)
ELSE 0.00
END as hit_accuracy,
s.tscore as total_score,
(s.n300 + s.n100 + s.n50) as total_hits,
s.max_combo as maximum_combo,
-- 游戏统计
s.plays as play_count,
s.playtime as play_time,
0 as replays_watched_by_others,
CASE WHEN s.pp > 0 THEN 1 ELSE 0 END as is_ranked,
-- 成绩等级计数
0 as grade_ss,
0 as grade_ssh,
0 as grade_s,
0 as grade_sh,
0 as grade_a
FROM stats s
WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = s.id)
ON DUPLICATE KEY UPDATE
count_100 = VALUES(count_100),
count_300 = VALUES(count_300),
count_50 = VALUES(count_50),
count_miss = VALUES(count_miss),
pp = VALUES(pp),
ranked_score = VALUES(ranked_score),
hit_accuracy = VALUES(hit_accuracy),
total_score = VALUES(total_score),
total_hits = VALUES(total_hits),
maximum_combo = VALUES(maximum_combo),
play_count = VALUES(play_count),
play_time = VALUES(play_time),
is_ranked = VALUES(is_ranked);
-- ============================================
-- 同步用户成就数据
-- ============================================
-- 从 user_achievements 表同步数据(如果存在的话)
INSERT IGNORE INTO lazer_user_achievements (
user_id,
achievement_id,
achieved_at
)
SELECT
ua.userid as user_id,
ua.achid as achievement_id,
NOW() as achieved_at -- 使用当前时间作为获得时间
FROM user_achievements ua
WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = ua.userid);
-- ============================================
-- 创建基础 OAuth 令牌记录(如果需要的话)
-- ============================================
-- 注意: OAuth 令牌通常在用户登录时动态创建,这里不需要预先填充
-- ============================================
-- 同步完成提示
-- ============================================
-- 显示同步统计信息
SELECT
'lazer_user_profiles' as table_name,
COUNT(*) as synced_records
FROM lazer_user_profiles
UNION ALL
SELECT
'lazer_user_countries' as table_name,
COUNT(*) as synced_records
FROM lazer_user_countries
UNION ALL
SELECT
'lazer_user_statistics' as table_name,
COUNT(*) as synced_records
FROM lazer_user_statistics
UNION ALL
SELECT
'lazer_user_achievements' as table_name,
COUNT(*) as synced_records
FROM lazer_user_achievements;
-- 显示一些样本数据
SELECT
u.id,
u.name,
lup.is_supporter,
lup.playmode,
luc.code as country_code,
lus.pp,
lus.play_count
FROM users u
LEFT JOIN lazer_user_profiles lup ON u.id = lup.user_id
LEFT JOIN lazer_user_countries luc ON u.id = luc.user_id
LEFT JOIN lazer_user_statistics lus ON u.id = lus.user_id AND lus.mode = 'osu'
ORDER BY u.id
LIMIT 10;

View File

@@ -1,64 +0,0 @@
from __future__ import annotations
import os
import requests
CLIENT_ID = os.environ.get("OSU_CLIENT_ID", "5")
CLIENT_SECRET = os.environ.get(
"OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
)
API_URL = os.environ.get("OSU_API_URL", "https://osu.ppy.sh")
def authenticate(username: str, password: str):
"""Authenticate via OAuth password flow and return the token dict."""
url = f"{API_URL}/oauth/token"
data = {
"grant_type": "password",
"username": username,
"password": password,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "*",
}
response = requests.post(url, data=data)
response.raise_for_status()
return response.json()
def refresh_token(refresh: str):
"""Refresh the OAuth token."""
url = f"{API_URL}/oauth/token"
data = {
"grant_type": "refresh_token",
"refresh_token": refresh,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "*",
}
response = requests.post(url, data=data)
response.raise_for_status()
return response.json()
def get_current_user(access_token: str, ruleset: str = "osu"):
"""Retrieve the authenticated user's data."""
url = f"{API_URL}/api/v2/me/{ruleset}"
headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
import getpass
username = input("osu! username: ")
password = getpass.getpass()
token = authenticate(username, password)
print("Access Token:", token["access_token"])
user = get_current_user(token["access_token"])
print(user)

View File

@@ -1,128 +0,0 @@
#!/usr/bin/env python3
"""
简化的数据同步执行脚本
直接使用项目配置执行数据同步
"""
from __future__ import annotations
import os
import subprocess
from urllib.parse import urlparse
from app.config import settings
def parse_database_url():
"""解析数据库 URL"""
url = urlparse(settings.DATABASE_URL)
return {
"host": url.hostname or "localhost",
"port": url.port or 3306,
"user": url.username or "root",
"password": url.password or "",
"database": url.path.lstrip("/") if url.path else "osu_api",
}
def run_sql_script(script_path: str):
"""使用 mysql 命令行执行 SQL 脚本"""
if not os.path.exists(script_path):
print(f"错误: SQL 脚本不存在 - {script_path}")
return False
# 解析数据库配置
db_config = parse_database_url()
# 构建 mysql 命令
cmd = [
"mysql",
f"--host={db_config['host']}",
f"--port={db_config['port']}",
f"--user={db_config['user']}",
db_config["database"],
]
# 添加密码(如果有的话)
if db_config["password"]:
cmd.insert(-1, f"--password={db_config['password']}")
try:
print(f"执行 SQL 脚本: {script_path}")
with open(script_path, encoding="utf-8") as f:
result = subprocess.run(
cmd, stdin=f, capture_output=True, text=True, check=True
)
if result.stdout:
print("执行结果:")
print(result.stdout)
print(f"✓ 成功执行: {script_path}")
return True
except subprocess.CalledProcessError as e:
print(f"✗ 执行失败: {script_path}")
print(f"错误信息: {e.stderr}")
return False
except FileNotFoundError:
print("错误: 未找到 mysql 命令行工具")
print("请确保 MySQL 客户端已安装并添加到 PATH 环境变量中")
return False
def main():
"""主函数"""
print("Lazer API 快速数据同步")
print("=" * 40)
db_config = parse_database_url()
print(f"数据库: {db_config['host']}:{db_config['port']}/{db_config['database']}")
print()
# 确认是否继续
print("这将执行以下操作:")
print("1. 创建 lazer 专用表结构")
print("2. 同步现有用户数据到新表")
print("3. 不会修改现有的原始表数据")
print()
confirm = input("是否继续? (y/N): ").strip().lower()
if confirm != "y":
print("操作已取消")
return
# 获取脚本路径
script_dir = os.path.dirname(__file__)
migrations_dir = os.path.join(script_dir, "migrations_old")
# 第一步: 创建表结构
print("\n步骤 1: 创建 lazer 专用表结构...")
add_fields_script = os.path.join(migrations_dir, "add_missing_fields.sql")
if not run_sql_script(add_fields_script):
print("表结构创建失败,停止执行")
return
# 第二步: 同步数据
print("\n步骤 2: 同步历史数据...")
sync_script = os.path.join(migrations_dir, "sync_legacy_data.sql")
if not run_sql_script(sync_script):
print("数据同步失败")
return
# 第三步: 添加缺失的字段
print("\n步骤 3: 添加缺失的字段...")
add_rank_fields_script = os.path.join(migrations_dir, "add_lazer_rank_fields.sql")
if not run_sql_script(add_rank_fields_script):
print("添加字段失败")
return
print("\n🎉 数据同步完成!")
print("\n现在您可以:")
print("1. 启动 Lazer API 服务器")
print("2. 使用现有用户账号登录")
print("3. 查看同步后的用户数据")
if __name__ == "__main__":
main()

View File

@@ -1,52 +0,0 @@
#!/usr/bin/env python3
"""
Script to remove ANSI escape codes from log files
"""
from __future__ import annotations
import re
import sys
def remove_ansi_codes(text):
"""
Remove ANSI escape codes from text
"""
# Regular expression to match ANSI escape codes
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
return ansi_escape.sub("", text)
def process_log_file(input_file, output_file=None):
"""
Process log file and remove ANSI escape codes
"""
if output_file is None:
output_file = (
input_file.replace(".log", "_clean.log")
if ".log" in input_file
else input_file + "_clean"
)
with open(input_file, "r", encoding="utf-8") as infile:
content = infile.read()
# Remove ANSI escape codes
clean_content = remove_ansi_codes(content)
with open(output_file, "w", encoding="utf-8") as outfile:
outfile.write(clean_content)
print(f"Processed {input_file} -> {output_file}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python remove_ansi.py <input_file> [output_file]")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else None
process_log_file(input_file, output_file)

View File

@@ -1,236 +0,0 @@
#!/usr/bin/env python3
"""
Lazer API 数据同步脚本
用于将现有的 bancho.py 数据同步到新的 lazer 专用表中
"""
from __future__ import annotations
import logging
import os
import sys
import pymysql
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("data_sync.log"), logging.StreamHandler(sys.stdout)],
)
logger = logging.getLogger(__name__)
class DatabaseSyncer:
def __init__(self, host: str, port: int, user: str, password: str, database: str):
"""初始化数据库连接配置"""
self.host = host
self.port = port
self.user = user
self.password = password
self.database = database
self.connection = None
def connect(self):
"""连接到数据库"""
try:
self.connection = pymysql.connect(
host=self.host,
port=self.port,
user=self.user,
password=self.password,
database=self.database,
charset="utf8mb4",
autocommit=False,
)
logger.info(f"成功连接到数据库 {self.database}")
except Exception as e:
logger.error(f"连接数据库失败: {e}")
raise
def disconnect(self):
"""断开数据库连接"""
if self.connection:
self.connection.close()
logger.info("数据库连接已关闭")
def execute_sql_file(self, file_path: str):
"""执行 SQL 文件"""
if not os.path.exists(file_path):
logger.error(f"SQL 文件不存在: {file_path}")
return False
try:
with open(file_path, encoding="utf-8") as f:
sql_content = f.read()
# 分割SQL语句简单实现按分号分割
statements = [
stmt.strip() for stmt in sql_content.split(";") if stmt.strip()
]
cursor = self.connection.cursor()
for i, statement in enumerate(statements):
# 跳过注释和空语句
if statement.startswith("--") or not statement:
continue
try:
logger.info(f"执行第 {i + 1}/{len(statements)} 条SQL语句...")
cursor.execute(statement)
# 如果是SELECT语句显示结果
if statement.strip().upper().startswith("SELECT"):
results = cursor.fetchall()
if results:
logger.info(f"查询结果: {results}")
except Exception as e:
logger.error(f"执行SQL语句失败: {statement[:100]}...")
logger.error(f"错误信息: {e}")
# 继续执行其他语句
continue
self.connection.commit()
cursor.close()
logger.info(f"成功执行SQL文件: {file_path}")
return True
except Exception as e:
logger.error(f"执行SQL文件失败: {e}")
if self.connection:
self.connection.rollback()
return False
def check_tables_exist(self, tables: list) -> dict:
"""检查表是否存在"""
results = {}
cursor = self.connection.cursor()
for table in tables:
try:
cursor.execute(f"SHOW TABLES LIKE '{table}'")
exists = cursor.fetchone() is not None
results[table] = exists
logger.info(f"'{table}' {'存在' if exists else '不存在'}")
except Exception as e:
logger.error(f"检查表 '{table}' 时出错: {e}")
results[table] = False
cursor.close()
return results
def get_table_count(self, table: str) -> int:
"""获取表的记录数"""
try:
cursor = self.connection.cursor()
cursor.execute(f"SELECT COUNT(*) FROM {table}")
result = cursor.fetchone()
count = result[0] if result else 0
cursor.close()
return count
except Exception as e:
logger.error(f"获取表 '{table}' 记录数失败: {e}")
return -1
def main():
"""主函数"""
print("Lazer API 数据同步工具")
print("=" * 50)
# 数据库配置
db_config = {
"host": input("数据库主机 [localhost]: ").strip() or "localhost",
"port": int(input("数据库端口 [3306]: ").strip() or "3306"),
"user": input("数据库用户名: ").strip(),
"password": input("数据库密码: ").strip(),
"database": input("数据库名称: ").strip(),
}
syncer = DatabaseSyncer(**db_config)
try:
# 连接数据库
syncer.connect()
# 检查必要的原始表是否存在
required_tables = ["users", "stats"]
table_status = syncer.check_tables_exist(required_tables)
missing_tables = [table for table, exists in table_status.items() if not exists]
if missing_tables:
logger.error(f"缺少必要的原始表: {missing_tables}")
return
# 显示原始表的记录数
for table in required_tables:
count = syncer.get_table_count(table)
logger.info(f"'{table}' 当前有 {count} 条记录")
# 确认是否执行同步
print("\n准备执行数据同步...")
print("这将会:")
print("1. 创建 lazer 专用表结构 (如果不存在)")
print("2. 从现有表同步数据到新表")
print("3. 不会修改或删除现有数据")
confirm = input("\n是否继续? (y/N): ").strip().lower()
if confirm != "y":
print("操作已取消")
return
# 执行表结构创建
migrations_dir = os.path.join(os.path.dirname(__file__), "migrations_old")
print("\n步骤 1: 创建表结构...")
add_fields_sql = os.path.join(migrations_dir, "add_missing_fields.sql")
if os.path.exists(add_fields_sql):
success = syncer.execute_sql_file(add_fields_sql)
if not success:
logger.error("创建表结构失败")
return
else:
logger.warning(f"表结构文件不存在: {add_fields_sql}")
# 执行数据同步
print("\n步骤 2: 同步数据...")
sync_sql = os.path.join(migrations_dir, "sync_legacy_data.sql")
if os.path.exists(sync_sql):
success = syncer.execute_sql_file(sync_sql)
if not success:
logger.error("数据同步失败")
return
else:
logger.error(f"同步脚本不存在: {sync_sql}")
return
# 显示同步后的统计信息
print("\n步骤 3: 同步完成统计...")
lazer_tables = [
"lazer_user_profiles",
"lazer_user_countries",
"lazer_user_statistics",
"lazer_user_kudosu",
"lazer_user_counts",
]
for table in lazer_tables:
count = syncer.get_table_count(table)
if count >= 0:
logger.info(f"'{table}' 现在有 {count} 条记录")
print("\n数据同步完成!")
except KeyboardInterrupt:
print("\n\n操作被用户中断")
except Exception as e:
logger.error(f"同步过程中发生错误: {e}")
finally:
syncer.disconnect()
if __name__ == "__main__":
main()

View File

@@ -1,256 +0,0 @@
#!/usr/bin/env python3
"""
测试 osu! API 模拟服务器的脚本
"""
from __future__ import annotations
import os
from dotenv import load_dotenv
import httpx as requests
# 加载 .env 文件
load_dotenv()
CLIENT_ID = os.environ.get("OSU_CLIENT_ID", "5")
CLIENT_SECRET = os.environ.get(
"OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
)
API_URL = os.environ.get("OSU_API_URL", "http://localhost:8000")
def test_server_health():
"""测试服务器健康状态"""
try:
response = requests.get(f"{API_URL}/health")
if response.status_code == 200:
print("✅ 服务器健康检查通过")
return True
else:
print(f"❌ 服务器健康检查失败: {response.status_code}")
return False
except Exception as e:
print(f"❌ 无法连接到服务器: {e}")
return False
def authenticate(username: str, password: str):
"""通过 OAuth 密码流进行身份验证并返回令牌字典"""
url = f"{API_URL}/oauth/token"
data = {
"grant_type": "password",
"username": username,
"password": password,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "*",
}
try:
response = requests.post(url, data=data)
if response.status_code == 200:
print("✅ 身份验证成功")
return response.json()
else:
print(f"❌ 身份验证失败: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except Exception as e:
print(f"❌ 身份验证请求失败: {e}")
return None
def refresh_token(refresh_token: str):
"""刷新 OAuth 令牌"""
url = f"{API_URL}/oauth/token"
data = {
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "*",
}
try:
response = requests.post(url, data=data)
if response.status_code == 200:
print("✅ 令牌刷新成功")
return response.json()
else:
print(f"❌ 令牌刷新失败: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except Exception as e:
print(f"❌ 令牌刷新请求失败: {e}")
return None
def get_current_user(access_token: str, ruleset: str = "osu"):
"""获取认证用户的数据"""
url = f"{API_URL}/api/v2/me/{ruleset}"
headers = {"Authorization": f"Bearer {access_token}"}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"✅ 成功获取 {ruleset} 模式的用户数据")
return response.json()
else:
print(f"❌ 获取用户数据失败: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except Exception as e:
print(f"❌ 获取用户数据请求失败: {e}")
return None
def get_beatmap_scores(access_token: str, beatmap_id: int):
"""获取谱面成绩数据"""
url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores"
headers = {"Authorization": f"Bearer {access_token}"}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"✅ 成功获取谱面 {beatmap_id} 的成绩数据")
return response.json()
else:
print(f"❌ 获取谱面成绩失败: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except Exception as e:
print(f"❌ 获取谱面成绩请求失败: {e}")
return None
def get_user_beatmap_score(access_token: str, beatmap_id: int, user_id: int):
"""获取玩家成绩"""
url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores/users/{user_id}"
headers = {"Authorization": f"Bearer {access_token}"}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"✅ 成功获取谱面 {beatmap_id} 中用户 {user_id} 的成绩数据")
return response.json()
else:
print(f"❌ 获取谱面成绩失败: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except Exception as e:
print(f"❌ 获取谱面成绩请求失败: {e}")
return None
def get_user_beatmap_score_all(access_token: str, beatmap_id: int, user_id: int):
"""获取玩家成绩"""
url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores/users/{user_id}/all"
headers = {"Authorization": f"Bearer {access_token}"}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"✅ 成功获取谱面 {beatmap_id} 中用户 {user_id} 的成绩数据")
return response.json()
else:
print(f"❌ 获取谱面成绩失败: {response.status_code}")
print(f"响应内容: {response.text}")
return None
except Exception as e:
print(f"❌ 获取谱面成绩请求失败: {e}")
return None
def main():
"""主测试函数"""
print("=== osu! API 模拟服务器测试 ===\n")
# 1. 测试服务器健康状态
print("1. 检查服务器状态...")
if not test_server_health():
print("请确保服务器正在运行: uvicorn main:app --reload")
return
print()
# 2. 获取用户凭据
print("2. 用户身份验证...")
username = input("请输入用户名 (默认: Googujiang): ").strip() or "Googujiang"
import getpass
password = getpass.getpass("请输入密码 (默认: password123): ") or "password123"
# 3. 身份验证
print(f"\n3. 正在验证用户 '{username}'...")
token_data = authenticate(username, password)
if not token_data:
print("身份验证失败,请检查用户名和密码")
return
print(f"访问令牌: {token_data['access_token']}")
print(f"刷新令牌: {token_data['refresh_token']}")
print(f"令牌有效期: {token_data['expires_in']}")
# 4. 获取用户数据
print("\n4. 获取用户数据...")
for ruleset in ["osu", "taiko", "fruits", "mania"]:
print(f"\n--- {ruleset.upper()} 模式 ---")
user_data = get_current_user(token_data["access_token"], ruleset)
if user_data:
print(f"用户名: {user_data['username']}")
print(f"国家: {user_data['country']['name']} ({user_data['country_code']})")
print(f"全球排名: {user_data['statistics']['global_rank']}")
print(f"PP: {user_data['statistics']['pp']}")
print(f"游戏次数: {user_data['statistics']['play_count']}")
print(f"命中精度: {user_data['statistics']['hit_accuracy']:.2f}%")
# 5. 测试获取谱面成绩
print("\n5. 测试获取谱面成绩...")
scores_data = get_beatmap_scores(token_data["access_token"], 1)
if scores_data:
print(f"谱面成绩总数: {len(scores_data['scores'])}")
if scores_data["userScore"]:
print("用户在该谱面有成绩记录")
print(f"用户成绩 ID: {scores_data['userScore']['id']}")
print(f"用户成绩分数: {scores_data['userScore']['total_score']}\n")
else:
print("用户在该谱面没有成绩记录\n")
# 5a. 测试谱面指定用户成绩
user_score = get_user_beatmap_score(token_data["access_token"], 1, 1)
if user_score:
print(f"用户成绩ID:{user_score['score']['id']}")
print(f"此成绩acc:{user_score['score']['accuracy']}")
print(f"总分:{user_score['score']['classic_total_score']}\n")
else:
print("该用户在此谱面没有记录\n")
# 5b. 测试谱面指定用户成绩
user_score_all = get_user_beatmap_score_all(token_data["access_token"], 1, 1)
if user_score_all:
index = 1
for score in user_score_all:
print(f"{index}个成绩:")
print(f"用户成绩ID:{score['id']}")
print(f"此成绩acc:{score['accuracy']}")
print(f"总分:{score['classic_total_score']}")
else:
print("该用户在此谱面没有记录")
# 6. 测试令牌刷新
print("\n6. 测试令牌刷新...")
new_token_data = refresh_token(token_data["refresh_token"])
if new_token_data:
print(f"新访问令牌: {new_token_data['access_token']}")
# 使用新令牌获取用户数据
print("\n6. 使用新令牌获取用户数据...")
user_data = get_current_user(new_token_data["access_token"])
if user_data:
print(f"✅ 新令牌有效,用户: {user_data['username']}")
print("\n=== 测试完成 ===")
if __name__ == "__main__":
main()

View File

@@ -1,133 +0,0 @@
#!/usr/bin/env python3
"""
Lazer API 系统测试脚本
验证新的 lazer 表支持是否正常工作
"""
from __future__ import annotations
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from app.database import User
from app.dependencies.database import engine
from app.utils import convert_db_user_to_api_user
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
async def test_lazer_tables():
"""测试 lazer 表的基本功能"""
print("测试 Lazer API 表支持...")
async with AsyncSession(engine) as session:
async with session.begin():
try:
# 测试查询用户
statement = select(User)
result = await session.execute(statement)
user = result.scalars().first()
if not user:
print("❌ 没有找到用户,请先同步数据")
return False
print(f"✓ 找到用户: {user.name} (ID: {user.id})")
# 测试 lazer 资料
if user.lazer_profile:
print(
f"✓ 用户有 lazer 资料: 支持者={user.lazer_profile.is_supporter}"
)
else:
print("⚠ 用户没有 lazer 资料,将使用默认值")
# 测试 lazer 统计
osu_stats = None
for stat in user.lazer_statistics:
if stat.mode == "osu":
osu_stats = stat
break
if osu_stats:
print(
f"✓ 用户有 osu! 统计: PP={osu_stats.pp}, "
f"游戏次数={osu_stats.play_count}"
)
else:
print("⚠ 用户没有 osu! 统计,将使用默认值")
# 测试转换为 API 格式
api_user = convert_db_user_to_api_user(user, "osu")
print("✓ 成功转换为 API 用户格式")
print(f" - 用户名: {api_user.username}")
print(f" - 国家: {api_user.country_code}")
print(f" - PP: {api_user.statistics.pp}")
print(f" - 是否支持者: {api_user.is_supporter}")
return True
except Exception as e:
print(f"❌ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
async def test_authentication():
"""测试认证功能"""
print("\n测试认证功能...")
async with AsyncSession(engine) as session:
async with session.begin():
try:
# 尝试认证第一个用户
statement = select(User)
result = await session.execute(statement)
user = result.scalars().first()
if not user:
print("❌ 没有用户进行认证测试")
return False
print(f"✓ 测试用户: {user.name}")
print("⚠ 注意: 实际密码认证需要正确的密码")
return True
except Exception as e:
print(f"❌ 认证测试失败: {e}")
return False
async def main():
"""主测试函数"""
print("Lazer API 系统测试")
print("=" * 40)
# 测试表连接
success1 = await test_lazer_tables()
# 测试认证
success2 = await test_authentication()
print("\n" + "=" * 40)
if success1 and success2:
print("🎉 所有测试通过!")
print("\n现在可以:")
print("1. 启动 API 服务器: python main.py")
print("2. 测试 OAuth 认证")
print("3. 调用 /api/v2/me/osu 获取用户信息")
else:
print("❌ 测试失败,请检查:")
print("1. 数据库连接是否正常")
print("2. 是否已运行数据同步脚本")
print("3. lazer 表是否正确创建")
if __name__ == "__main__":
import asyncio
asyncio.run(main())

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env python3
"""
测试密码哈希和验证逻辑
"""
from __future__ import annotations
import hashlib
from app.auth import bcrypt_cache, get_password_hash, verify_password_legacy
def test_password_logic():
"""测试密码逻辑"""
print("=== 测试密码哈希和验证逻辑 ===\n")
# 测试密码
password = "password123"
print(f"原始密码: {password}")
# 1. 生成哈希
print("\n1. 生成密码哈希...")
hashed = get_password_hash(password)
print(f"bcrypt 哈希: {hashed}")
# 2. 验证密码
print("\n2. 验证密码...")
is_valid = verify_password_legacy(password, hashed)
print(f"验证结果: {'✅ 成功' if is_valid else '❌ 失败'}")
# 3. 测试错误密码
print("\n3. 测试错误密码...")
wrong_password = "wrongpassword"
is_valid_wrong = verify_password_legacy(wrong_password, hashed)
print(f"错误密码验证结果: {'❌ 不应该成功' if is_valid_wrong else '✅ 正确拒绝'}")
# 4. 测试缓存
print("\n4. 缓存状态:")
print(f"缓存中的条目数: {len(bcrypt_cache)}")
if hashed in bcrypt_cache:
print(f"缓存的 MD5: {bcrypt_cache[hashed]}")
expected_md5 = hashlib.md5(password.encode()).hexdigest().encode()
print(f"期望的 MD5: {expected_md5}")
print(f"缓存匹配: {'' if bcrypt_cache[hashed] == expected_md5 else ''}")
# 5. 再次验证(应该使用缓存)
print("\n5. 再次验证(使用缓存)...")
is_valid_cached = verify_password_legacy(password, hashed)
print(f"缓存验证结果: {'✅ 成功' if is_valid_cached else '❌ 失败'}")
print("\n=== 测试完成 ===")
if __name__ == "__main__":
test_password_logic()