From d8fcbf02cf35f29746f4bd6d965537b19a12b192 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 19 Jul 2025 14:45:15 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BB=8E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=AD=E8=AF=BB=E5=8F=96=E9=83=A8?= =?UTF-8?q?=E5=88=86lazer=E8=B5=84=E6=96=99=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 3 + .idea/inspectionProfiles/Project_Default.xml | 17 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 10 + .idea/modules.xml | 8 + .idea/osu_lazer_api.iml | 14 + .idea/vcs.xml | 6 + app/database copy.py | 403 ------------------ app/utils.py | 49 +-- main.py | 3 +- 10 files changed, 86 insertions(+), 433 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/osu_lazer_api.iml create mode 100644 .idea/vcs.xml delete mode 100644 app/database copy.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..d4add55 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e5787b9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..92ff71d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/osu_lazer_api.iml b/.idea/osu_lazer_api.iml new file mode 100644 index 0000000..e2e520d --- /dev/null +++ b/.idea/osu_lazer_api.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/database copy.py b/app/database copy.py deleted file mode 100644 index 7ba80bc..0000000 --- a/app/database copy.py +++ /dev/null @@ -1,403 +0,0 @@ -from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship -from datetime import datetime - -Base = declarative_base() - - -class User(Base): - __tablename__ = "users" - - # 主键 - id = Column(Integer, primary_key=True, index=True) - - # 基本信息(匹配 migrations 中的结构) - name = Column(String(32), unique=True, index=True, nullable=False) # 用户名 - safe_name = Column(String(32), unique=True, index=True, nullable=False) # 安全用户名 - email = Column(String(254), unique=True, index=True, nullable=False) - priv = Column(Integer, default=1, nullable=False) # 权限 - pw_bcrypt = Column(String(60), nullable=False) # bcrypt 哈希密码 - country = Column(String(2), default='CN', nullable=False) # 国家代码 - - # 状态和时间 - silence_end = Column(Integer, default=0, nullable=False) - donor_end = Column(Integer, default=0, nullable=False) - creation_time = Column(Integer, default=0, nullable=False) # Unix 时间戳 - latest_activity = Column(Integer, default=0, nullable=False) # Unix 时间戳 - - # 游戏相关 - preferred_mode = Column(Integer, default=0, nullable=False) # 偏好游戏模式 - play_style = Column(Integer, default=0, nullable=False) # 游戏风格 - - # 扩展信息 - clan_id = Column(Integer, default=0, nullable=False) - clan_priv = Column(Integer, default=0, nullable=False) - custom_badge_name = Column(String(16)) - custom_badge_icon = Column(String(64)) - userpage_content = Column(String(2048)) - api_key = Column(String(36), unique=True) - - # 虚拟字段用于兼容性 - @property - def username(self): - return self.name - - @property - def country_code(self): - return self.country - - @property - def join_date(self): - return datetime.fromtimestamp(self.creation_time) if self.creation_time > 0 else datetime.utcnow() - - @property - def last_visit(self): - return datetime.fromtimestamp(self.latest_activity) if self.latest_activity > 0 else None - - # 关联关系 - lazer_profile = relationship("LazerUserProfile", back_populates="user", uselist=False, cascade="all, delete-orphan") - lazer_statistics = relationship("LazerUserStatistics", back_populates="user", cascade="all, delete-orphan") - lazer_achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") - - -# ============================================ -# Lazer API 专用表模型 -# ============================================ - -class LazerUserProfile(Base): - __tablename__ = "lazer_user_profiles" - - user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) - - # 基本状态字段 - is_active = Column(Boolean, default=True) - is_bot = Column(Boolean, default=False) - is_deleted = Column(Boolean, default=False) - is_online = Column(Boolean, default=True) - is_supporter = Column(Boolean, default=False) - is_restricted = Column(Boolean, default=False) - session_verified = Column(Boolean, default=False) - has_supported = Column(Boolean, default=False) - pm_friends_only = Column(Boolean, default=False) - - # 基本资料字段 - default_group = Column(String(50), default='default') - last_visit = Column(DateTime) - join_date = Column(DateTime) - profile_colour = Column(String(7)) - profile_hue = Column(Integer) - - # 社交媒体和个人资料字段 - avatar_url = Column(String(500)) - cover_url = Column(String(500)) - discord = Column(String(100)) - twitter = Column(String(100)) - website = Column(String(500)) - title = Column(String(100)) - title_url = Column(String(500)) - interests = Column(Text) - location = Column(String(100)) - occupation = Column(String(100)) - - # 游戏相关字段 - playmode = Column(String(10), default='osu') - support_level = Column(Integer, default=0) - max_blocks = Column(Integer, default=100) - max_friends = Column(Integer, default=500) - post_count = Column(Integer, default=0) - - # 页面内容 - page_html = Column(Text) - page_raw = Column(Text) - - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - # 关联关系 - user = relationship("User", back_populates="lazer_profile") - - -class LazerUserCountry(Base): - __tablename__ = "lazer_user_countries" - - user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) - code = Column(String(2), nullable=False) - name = Column(String(100), nullable=False) - - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - -class LazerUserKudosu(Base): - __tablename__ = "lazer_user_kudosu" - - user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) - available = Column(Integer, default=0) - total = Column(Integer, default=0) - - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - -class LazerUserCounts(Base): - __tablename__ = "lazer_user_counts" - - user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) - - # 统计计数字段 - beatmap_playcounts_count = Column(Integer, default=0) - comments_count = Column(Integer, default=0) - favourite_beatmapset_count = Column(Integer, default=0) - follower_count = Column(Integer, default=0) - graveyard_beatmapset_count = Column(Integer, default=0) - guest_beatmapset_count = Column(Integer, default=0) - loved_beatmapset_count = Column(Integer, default=0) - mapping_follower_count = Column(Integer, default=0) - nominated_beatmapset_count = Column(Integer, default=0) - pending_beatmapset_count = Column(Integer, default=0) - ranked_beatmapset_count = Column(Integer, default=0) - ranked_and_approved_beatmapset_count = Column(Integer, default=0) - unranked_beatmapset_count = Column(Integer, default=0) - scores_best_count = Column(Integer, default=0) - scores_first_count = Column(Integer, default=0) - scores_pinned_count = Column(Integer, default=0) - scores_recent_count = Column(Integer, default=0) - - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - -class LazerUserStatistics(Base): - __tablename__ = "lazer_user_statistics" - - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - mode = Column(String(10), nullable=False, default='osu') - - # 基本命中统计 - count_100 = Column(Integer, default=0) - count_300 = Column(Integer, default=0) - count_50 = Column(Integer, default=0) - count_miss = Column(Integer, default=0) - - # 等级信息 - level_current = Column(Integer, default=1) - level_progress = Column(Integer, default=0) - - # 排名信息 - global_rank = Column(Integer) - global_rank_exp = Column(Integer) - country_rank = Column(Integer) - - # PP 和分数 - pp = Column(DECIMAL(10, 2), default=0.00) - pp_exp = Column(DECIMAL(10, 2), default=0.00) - ranked_score = Column(Integer, default=0) - hit_accuracy = Column(DECIMAL(5, 2), default=0.00) - total_score = Column(Integer, default=0) - total_hits = Column(Integer, default=0) - maximum_combo = Column(Integer, default=0) -# ============================================ -# 旧的兼容性表模型(保留以便向后兼容) -# ============================================ - -class LegacyUserStatistics(Base): - __tablename__ = "user_statistics" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania - - # 基本统计 - count_100 = Column(Integer, default=0) - count_300 = Column(Integer, default=0) - count_50 = Column(Integer, default=0) - count_miss = Column(Integer, default=0) - - # 等级信息 - level_current = Column(Integer, default=1) - level_progress = Column(Integer, default=0) - - # 排名信息 - global_rank = Column(Integer) - global_rank_exp = Column(Integer) - country_rank = Column(Integer) - - # PP 和分数 - pp = Column(Float, default=0.0) - pp_exp = Column(Float, default=0.0) - ranked_score = Column(Integer, default=0) - hit_accuracy = Column(Float, default=0.0) - total_score = Column(Integer, default=0) - total_hits = Column(Integer, default=0) - maximum_combo = Column(Integer, default=0) - - # 游戏统计 - play_count = Column(Integer, default=0) - play_time = Column(Integer, default=0) - replays_watched_by_others = Column(Integer, default=0) - is_ranked = Column(Boolean, default=False) - - # 成绩等级计数 - grade_ss = Column(Integer, default=0) - grade_ssh = Column(Integer, default=0) - grade_s = Column(Integer, default=0) - grade_sh = Column(Integer, default=0) - grade_a = Column(Integer, default=0) - - -class OAuthToken(Base): - __tablename__ = "oauth_tokens" - - id = Column(Integer, primary_key=True, autoincrement=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - access_token = Column(String(255), nullable=False, index=True) - refresh_token = Column(String(255), nullable=False, index=True) - expires_at = Column(DateTime, nullable=False) - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - previous_usernames = Column(JSON, default=list) - replays_watched_counts = Column(JSON, default=list) - - # 关联关系 - statistics = relationship("UserStatistics", back_populates="user", cascade="all, delete-orphan") - achievements = relationship("UserAchievement", back_populates="user", cascade="all, delete-orphan") - team_membership = relationship("TeamMember", back_populates="user", cascade="all, delete-orphan") - daily_challenge_stats = relationship("DailyChallengeStats", back_populates="user", uselist=False, cascade="all, delete-orphan") - rank_history = relationship("RankHistory", back_populates="user", cascade="all, delete-orphan") - - -class UserStatistics(Base): - __tablename__ = "user_statistics" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania - - # 基本统计 - count_100 = Column(Integer, default=0) - count_300 = Column(Integer, default=0) - count_50 = Column(Integer, default=0) - count_miss = Column(Integer, default=0) - - # 等级信息 - level_current = Column(Integer, default=1) - level_progress = Column(Integer, default=0) - - # 排名信息 - global_rank = Column(Integer) - global_rank_exp = Column(Integer) - country_rank = Column(Integer) - - # PP 和分数 - pp = Column(Float, default=0.0) - pp_exp = Column(Float, default=0.0) - ranked_score = Column(Integer, default=0) - hit_accuracy = Column(Float, default=0.0) - total_score = Column(Integer, default=0) - total_hits = Column(Integer, default=0) - maximum_combo = Column(Integer, default=0) - - # 游戏统计 - play_count = Column(Integer, default=0) - play_time = Column(Integer, default=0) # 秒 - replays_watched_by_others = Column(Integer, default=0) - is_ranked = Column(Boolean, default=False) - - # 成绩等级计数 - grade_ss = Column(Integer, default=0) - grade_ssh = Column(Integer, default=0) - grade_s = Column(Integer, default=0) - grade_sh = Column(Integer, default=0) - grade_a = Column(Integer, default=0) - - # 最高排名记录 - rank_highest = Column(Integer) - rank_highest_updated_at = Column(DateTime) - - created_at = Column(DateTime, default=datetime.utcnow) - updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - # 关联关系 - user = relationship("User", back_populates="statistics") - - -class UserAchievement(Base): - __tablename__ = "user_achievements" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - achievement_id = Column(Integer, nullable=False) - achieved_at = Column(DateTime, default=datetime.utcnow) - - user = relationship("User", back_populates="achievements") - - -class Team(Base): - __tablename__ = "teams" - - id = Column(Integer, primary_key=True, index=True) - name = Column(String(100), nullable=False) - short_name = Column(String(10), nullable=False) - flag_url = Column(String(500)) - created_at = Column(DateTime, default=datetime.utcnow) - - members = relationship("TeamMember", back_populates="team", cascade="all, delete-orphan") - - -class TeamMember(Base): - __tablename__ = "team_members" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - team_id = Column(Integer, ForeignKey("teams.id"), nullable=False) - joined_at = Column(DateTime, default=datetime.utcnow) - - user = relationship("User", back_populates="team_membership") - team = relationship("Team", back_populates="members") - - -class DailyChallengeStats(Base): - __tablename__ = "daily_challenge_stats" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True) - - daily_streak_best = Column(Integer, default=0) - daily_streak_current = Column(Integer, default=0) - last_update = Column(DateTime) - last_weekly_streak = Column(DateTime) - playcount = Column(Integer, default=0) - top_10p_placements = Column(Integer, default=0) - top_50p_placements = Column(Integer, default=0) - weekly_streak_best = Column(Integer, default=0) - weekly_streak_current = Column(Integer, default=0) - - user = relationship("User", back_populates="daily_challenge_stats") - - -class RankHistory(Base): - __tablename__ = "rank_history" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - mode = Column(String(10), nullable=False) - rank_data = Column(JSON, nullable=False) # Array of ranks - date_recorded = Column(DateTime, default=datetime.utcnow) - - user = relationship("User", back_populates="rank_history") - - -class OAuthToken(Base): - __tablename__ = "oauth_tokens" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - access_token = Column(String(500), unique=True, nullable=False) - refresh_token = Column(String(500), unique=True, nullable=False) - token_type = Column(String(20), default="Bearer") - scope = Column(String(100), default="*") - expires_at = Column(DateTime, nullable=False) - created_at = Column(DateTime, default=datetime.utcnow) - - user = relationship("User") diff --git a/app/utils.py b/app/utils.py index c762bff..f7ced49 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,7 +1,7 @@ from typing import Dict, List, Optional -from datetime import datetime -from app.models import * -from app.database import User as DBUser, LazerUserStatistics, LazerUserProfile, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement +from datetime import datetime, UTC +from app.models import User, Statistics, Level, GradeCounts, Country, Cover, Kudosu, GameMode, PlayStyle, Team, UserAchievement, RankHistory, DailyChallengeStats, RankHighest, Page +from app.database import User as DBUser, LazerUserProfile, LazerUserStatistics, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement from sqlalchemy.orm import Session @@ -32,9 +32,11 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio user_stats = create_default_lazer_statistics(ruleset) # 获取国家信息 + country_code = db_user.country_code if db_user.country_code else 'XX' + country = Country( - code=db_user.country_code, - name=get_country_name(db_user.country_code) + code=country_code, + name=get_country_name(country_code) ) # 获取 Kudosu 信息 @@ -186,14 +188,14 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio user_id = getattr(db_user, 'id') user_name = getattr(db_user, 'name') user_country = getattr(db_user, 'country') - + # 获取用户头像URL avatar_url = None # 首先检查 profile 中的 avatar_url if profile and hasattr(profile, 'avatar_url') and profile.avatar_url: avatar_url = str(profile.avatar_url) - + # 然后检查是否有关联的头像记录 if avatar_url is None and hasattr(db_user, 'avatar') and db_user.avatar is not None: if db_user.avatar.r2_game_url: @@ -202,13 +204,13 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio elif db_user.avatar.r2_original_url: # 其次使用原始头像URL avatar_url = str(db_user.avatar.r2_original_url) - + # 如果还是没有找到,通过查询获取 if db_session and avatar_url is None: try: # 导入UserAvatar模型 from app.database import UserAvatar - + # 尝试查找用户的头像记录 avatar_record = db_session.query(UserAvatar).filter_by(user_id=user_id, is_active=True).first() if avatar_record is not None: @@ -229,7 +231,7 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio id=user_id, username=user_name, avatar_url=avatar_url, # 使用我们上面获取的头像URL - country_code=user_country, + country_code=country_code, default_group=profile.default_group if profile else "default", is_active=profile.is_active if profile else True, is_bot=profile.is_bot if profile else False, @@ -237,27 +239,19 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio is_online=profile.is_online if profile else True, is_supporter=profile.is_supporter if profile else False, is_restricted=profile.is_restricted if profile else False, - last_visit=db_user.last_visit, + last_visit=db_user.last_visit if db_user.last_visit else None, pm_friends_only=profile.pm_friends_only if profile else False, profile_colour=profile.profile_colour if profile else None, - cover_url=cover_url, + cover_url=profile.cover_url if profile and profile.cover_url else "https://assets.ppy.sh/user-profile-covers/default.jpeg", discord=profile.discord if profile else None, has_supported=profile.has_supported if profile else False, interests=profile.interests if profile else None, - join_date=db_user.join_date, + join_date=db_user.join_date if db_user.join_date else None, location=profile.location if profile else None, - max_blocks=profile.max_blocks if profile else 100, - max_friends=profile.max_friends if profile else 500, - - occupation=None, # 职业字段,默认为 None #待修改 - - #playmode=GameMode(db_user.playmode), - playmode=GameMode("osu"), #待修改 - - playstyle=[PlayStyle.MOUSE, PlayStyle.KEYBOARD, PlayStyle.TABLET], #待修改 - - post_count=0, - profile_hue=None, + max_blocks=profile.max_blocks if profile and profile.max_blocks else 100, + max_friends=profile.max_friends if profile and profile.max_friends else 500, + post_count=profile.post_count if profile and profile.post_count else 0, + profile_hue=profile.profile_hue if profile and profile.profile_hue else None, profile_order= ['me', 'recent_activity', 'top_ranks', 'medals', 'historical', 'beatmaps', 'kudosu'], title=None, title_url=None, @@ -295,12 +289,11 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio daily_challenge_user_stats=None, groups=[], monthly_playcounts=[], - #page=Page(html=db_user.page_html, raw=db_user.page_raw), - page=Page(), # Provide a default Page object + page=Page(html=profile.page_html, raw=profile.page_raw) if profile.page_html or profile.page_raw else Page(), previous_usernames=[], rank_highest=rank_highest, rank_history=rank_history, - rankHistory=rank_history, # 兼容性别名 + rankHistory=rank_history, # 保留旧API兼容性字段 replays_watched_counts=[], team=team, user_achievements=user_achievements diff --git a/main.py b/main.py index 12780fb..f293cf2 100644 --- a/main.py +++ b/main.py @@ -20,7 +20,6 @@ app = FastAPI(title="osu! API 模拟服务器", version="1.0.0") security = HTTPBearer() - @app.post("/oauth/token", response_model=TokenResponse) async def oauth_token( grant_type: str = Form(...), @@ -104,7 +103,7 @@ async def oauth_token( refresh_token=new_refresh_token, scope=scope ) - + else: raise HTTPException(status_code=400, detail="Unsupported grant type") From 757166b6656dc104d425acafb27ad6c8de5c8ac0 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 19 Jul 2025 16:09:57 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=B5=84=E6=96=99=E6=9E=84=E5=BB=BA=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 LazerUserProfileSections 模型,用于自定义用户资料顺序 - 在 User 模型中添加 lazer_profile_order 关系 - 优化 utils.py 中的 build_user_profile 函数,使用数据库中的计数信息 -修复默认计数信息中的 recent_scores_count 和 socres_first_count 属性 --- app/database.py | 13 +++++++++++ app/utils.py | 59 ++++++++++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/app/database.py b/app/database.py index ed2ba61..8a12a94 100644 --- a/app/database.py +++ b/app/database.py @@ -1,4 +1,5 @@ from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL +from sqlalchemy.dialects.mysql import VARCHAR from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from datetime import datetime @@ -61,6 +62,7 @@ class User(Base): lazer_profile = relationship("LazerUserProfile", back_populates="user", uselist=False, cascade="all, delete-orphan") lazer_statistics = relationship("LazerUserStatistics", back_populates="user", cascade="all, delete-orphan") lazer_achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") + lazer_profile_order=relationship("LazerUserProfileOrder", back_populates="user", cascade="all, delete-orphan") statistics = relationship("LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan") achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") team_membership = relationship("TeamMember", back_populates="user", cascade="all, delete-orphan") @@ -126,6 +128,17 @@ class LazerUserProfile(Base): # 关联关系 user = relationship("User", back_populates="lazer_profile") +class LazerUserProfileSections(Base): + __tablename__ = "lazer_user_profile_sections" + + user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) + section_name = Column(VARCHAR(50)) + display_order=Column(Integer) + + created_at=Column(DateTime, default=datetime.utcnow) + updated_at=Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + user = relationship("User", back_populates="lazer_profile_order") class LazerUserCountry(Base): __tablename__ = "lazer_user_countries" diff --git a/app/utils.py b/app/utils.py index f7ced49..60080d5 100644 --- a/app/utils.py +++ b/app/utils.py @@ -19,7 +19,14 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio if not profile: # 如果没有 lazer 资料,使用默认值 profile = create_default_profile(db_user) - + + # 获取 Lazer 用户计数 + + lzrcnt=db_user.lazer_statistics + if not lzrcnt: + # 如果没有 lazer 计数,使用默认值 + lzrcnt = create_default_counts() + # 获取指定模式的统计信息 user_stats = None for stat in db_user.lazer_statistics: @@ -252,37 +259,37 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio max_friends=profile.max_friends if profile and profile.max_friends else 500, post_count=profile.post_count if profile and profile.post_count else 0, profile_hue=profile.profile_hue if profile and profile.profile_hue else None, - profile_order= ['me', 'recent_activity', 'top_ranks', 'medals', 'historical', 'beatmaps', 'kudosu'], - title=None, - title_url=None, - twitter=None, - website='https://gmoe.cc', + profile_order=profile.profile_order if profile and profile.profile_order else ['me', 'recent_activity', 'top_ranks', 'medals', 'historical', 'beatmaps', 'kudosu'], + title=profile.title if profile else None, + title_url=profile.title_url if profile else None, + twitter=profile.twitter if profile else None, + website=profile.website if profile else None, session_verified=True, - support_level=0, + support_level=profile.support_level if profile else 0, country=country, cover=cover, kudosu=kudosu, statistics=statistics, statistics_rulesets=statistics_rulesets, - beatmap_playcounts_count=3306, - comments_count=0, - favourite_beatmapset_count=0, - follower_count=0, - graveyard_beatmapset_count=0, - guest_beatmapset_count=0, - loved_beatmapset_count=0, - mapping_follower_count=0, - nominated_beatmapset_count=0, - pending_beatmapset_count=0, - ranked_beatmapset_count=0, - ranked_and_approved_beatmapset_count=0, - unranked_beatmapset_count=0, - scores_best_count=0, - scores_first_count=0, - scores_pinned_count=0, - scores_recent_count=0, + beatmap_playcounts_count=db_user.beatmap_playcounts_count if db_user.beatmap_playcounts_count is not None else 0, + comments_count=lzrcnt.comments_count if lzrcnt else 0, + favourite_beatmapset_count=lzrcnt.favourite_beatmapset_count if lzrcnt else 0, + follower_count=lzrcnt.follower_count if lzrcnt else 0, + graveyard_beatmapset_count=lzrcnt.graveyard_beatmapset_count if lzrcnt else 0, + guest_beatmapset_count=lzrcnt.guest_beatmapset_count if lzrcnt else 0, + loved_beatmapset_count=lzrcnt.loved_beatmapset_count if lzrcnt else 0, + mapping_follower_count=lzrcnt.mapping_follower_count if lzrcnt else 0, + nominated_beatmapset_count=lzrcnt.nominated_beatmapset_count if lzrcnt else 0, + pending_beatmapset_count=lzrcnt.pending_beatmapset_count if lzrcnt else 0, + ranked_beatmapset_count=lzrcnt.ranked_beatmapset_count if lzrcnt else 0, + ranked_and_approved_beatmapset_count=lzrcnt.ranked_and_approved_beatmapset_count if lzrcnt else 0, + unranked_beatmapset_count=lzrcnt.unranked_beatmapset_count if lzrcnt else 0, + scores_best_count=lzrcnt.scores_best_count if lzrcnt else 0, + scores_first_count=lzrcnt.socres_first_count if lzrcnt else 0, + scores_pinned_count=lzrcnt.scores_pinned_count, + scores_recent_count=lzrcnt.recent_scores_count if lzrcnt else 0, account_history=[], - active_tournament_banner=None, + active_tournament_banner=0, active_tournament_banners=[], badges=[], current_season_stats=None, @@ -419,6 +426,8 @@ def create_default_counts(): """创建默认的计数信息""" class MockCounts: def __init__(self): + self.recent_scores_count = None + self.socres_first_count = None self.beatmap_playcounts_count = 0 self.comments_count = 0 self.favourite_beatmapset_count = 0 From 91f15d572d1f967d966300286d69e604d49e216d Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 19 Jul 2025 20:43:36 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(database):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=B5=84=E6=96=99=E7=9B=B8=E5=85=B3=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A8=A1=E5=9E=8B=E5=92=8C=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 LazerUserProfileSections、LazerUserBanners、LazerUserBadge、LazerUserMonthlyPlaycounts、LazerUserPreviousUsername 和 LazerUserReplaysWatched 数据模型- 更新 User 数据模型,添加与新模型的关联关系 - 修改 convert_db_user_to_api_user 函数,支持新数据模型的处理- 完善 MockProfile 类,添加新模型的默认值和辅助方法 --- app/database.py | 86 +++++++++++++++++++++++++-- app/utils.py | 154 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 213 insertions(+), 27 deletions(-) diff --git a/app/database.py b/app/database.py index 8a12a94..b6f6a86 100644 --- a/app/database.py +++ b/app/database.py @@ -1,5 +1,5 @@ from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL -from sqlalchemy.dialects.mysql import VARCHAR +from sqlalchemy.dialects.mysql import VARCHAR, TINYINT from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from datetime import datetime @@ -62,14 +62,18 @@ class User(Base): lazer_profile = relationship("LazerUserProfile", back_populates="user", uselist=False, cascade="all, delete-orphan") lazer_statistics = relationship("LazerUserStatistics", back_populates="user", cascade="all, delete-orphan") lazer_achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") - lazer_profile_order=relationship("LazerUserProfileOrder", back_populates="user", cascade="all, delete-orphan") + lazer_profile_sections=relationship("LazerUserProfileSection", back_populates="user", cascade="all, delete-orphan") statistics = relationship("LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan") achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") team_membership = relationship("TeamMember", back_populates="user", cascade="all, delete-orphan") daily_challenge_stats = relationship("DailyChallengeStats", back_populates="user", uselist=False, cascade="all, delete-orphan") rank_history = relationship("RankHistory", back_populates="user", cascade="all, delete-orphan") avatar = relationship("UserAvatar", back_populates="user", primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)", uselist=False) - + active_banners=relationship("LazerUserBanners",back_populates="user",cascade="all, delete-orphan") + lazer_badges = relationship("LazerUserBadge", back_populates="user", cascade="all, delete-orphan") + lazer_monthly_playcounts = relationship("LazerUserMonthlyPlaycounts", back_populates="user", cascade="all, delete-orphan") + lazer_previous_usernames = relationship("LazerUserPreviousUsername", back_populates="user", cascade="all, delete-orphan") + lazer_replays_watched = relationship("LazerUserReplaysWatched", back_populates="user", cascade="all, delete-orphan") # ============================================ # Lazer API 专用表模型 @@ -131,8 +135,9 @@ class LazerUserProfile(Base): class LazerUserProfileSections(Base): __tablename__ = "lazer_user_profile_sections" - user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) - section_name = Column(VARCHAR(50)) + id=Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey("users.id"),nullable=False) + section_name = Column(VARCHAR(50),nullable=False) display_order=Column(Integer) created_at=Column(DateTime, default=datetime.utcnow) @@ -167,7 +172,7 @@ class LazerUserCounts(Base): user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) - # 统计计数字段 + # 统计计数字段f beatmap_playcounts_count = Column(Integer, default=0) comments_count = Column(Integer, default=0) favourite_beatmapset_count = Column(Integer, default=0) @@ -243,6 +248,17 @@ class LazerUserStatistics(Base): # 关联关系 user = relationship("User", back_populates="lazer_statistics") +class LazerUserBanners(Base): + __tablename__ = "lazer_user_tournament_banners" + + id=Column(Integer,primary_key=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + tournament_id=Column(Integer, nullable=False) + image_url = Column(VARCHAR(500),nullable=False) + is_active=Column(TINYINT(1)) + + user=relationship("User", back_populates="lazer_active_banners") + class LazerUserAchievement(Base): __tablename__ = "lazer_user_achievements" @@ -257,6 +273,64 @@ class LazerUserAchievement(Base): user = relationship("User", back_populates="lazer_achievements") +class LazerUserBadge(Base): + __tablename__ = "lazer_user_badges" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + badge_id = Column(Integer, nullable=False) + awarded_at = Column(DateTime) + description = Column(Text) + image_url = Column(String(500)) + url = Column(String(500)) + + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + user = relationship("User", back_populates="lazer_badges") + + +class LazerUserMonthlyPlaycounts(Base): + __tablename__ = "lazer_user_monthly_playcounts" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + start_date = Column(Date, nullable=False) + play_count = Column(Integer, default=0) + + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + user = relationship("User", back_populates="lazer_monthly_playcounts") + + +class LazerUserPreviousUsername(Base): + __tablename__ = "lazer_user_previous_usernames" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + username = Column(String(32), nullable=False) + changed_at = Column(DateTime, nullable=False) + + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + user = relationship("User", back_populates="lazer_previous_usernames") + + +class LazerUserReplaysWatched(Base): + __tablename__ = "lazer_user_replays_watched" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + start_date = Column(Date, nullable=False) + count = Column(Integer, default=0) + + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + user = relationship("User", back_populates="lazer_replays_watched") + # ============================================ # 旧的兼容性表模型(保留以便向后兼容) # ============================================ diff --git a/app/utils.py b/app/utils.py index 60080d5..9ee96ae 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,4 +1,3 @@ -from typing import Dict, List, Optional from datetime import datetime, UTC from app.models import User, Statistics, Level, GradeCounts, Country, Cover, Kudosu, GameMode, PlayStyle, Team, UserAchievement, RankHistory, DailyChallengeStats, RankHighest, Page from app.database import User as DBUser, LazerUserProfile, LazerUserStatistics, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement @@ -234,10 +233,68 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio if avatar_url is None: avatar_url = f"https://a.gu-osu.gmoe.cc/api/users/avatar/1" + # 处理 profile_order 列表排序 + profile_order = [] + if profile and profile.profile_order: + profile_order = sorted( + profile.profile_order, + key=lambda x: (x.display_order == 0, x.display_order) + ) + + # 在convert_db_user_to_api_user函数中添加active_tournament_banners处理 + active_tournament_banners = [] + if hasattr(db_user, 'lazer_tournament_banners') and db_user.lazer_tournament_banners: + for banner in db_user.lazer_tournament_banners: + active_tournament_banners.append({ + "tournament_id": banner.tournament_id, + "image_url": banner.image_url, + "is_active": banner.is_active + }) + + # 在convert_db_user_to_api_user函数中添加badges处理 + badges = [] + if hasattr(db_user, 'lazer_badges') and db_user.lazer_badges: + for badge in db_user.lazer_badges: + badges.append({ + "badge_id": badge.badge_id, + "awarded_at": badge.awarded_at, + "description": badge.description, + "image_url": badge.image_url, + "url": badge.url + }) + + # 在convert_db_user_to_api_user函数中添加monthly_playcounts处理 + monthly_playcounts = [] + if hasattr(db_user, 'lazer_monthly_playcounts') and db_user.lazer_monthly_playcounts: + for playcount in db_user.lazer_monthly_playcounts: + monthly_playcounts.append({ + "start_date": playcount.start_date.isoformat() if playcount.start_date else None, + "play_count": playcount.play_count + }) + + # 在convert_db_user_to_api_user函数中添加previous_usernames处理 + previous_usernames = [] + if hasattr(db_user, 'lazer_previous_usernames') and db_user.lazer_previous_usernames: + for username in db_user.lazer_previous_usernames: + previous_usernames.append({ + "username": username.username, + "changed_at": username.changed_at.isoformat() if username.changed_at else None + }) + + # 在convert_db_user_to_api_user函数中添加replays_watched_counts处理 + replays_watched_counts = [] + if hasattr(db_user, 'lazer_replays_watched') and db_user.lazer_replays_watched: + for replay in db_user.lazer_replays_watched: + replays_watched_counts.append({ + "start_date": replay.start_date.isoformat() if replay.start_date else None, + "count": replay.count + }) + + # 创建用户对象 user = User( id=user_id, username=user_name, - avatar_url=avatar_url, # 使用我们上面获取的头像URL + avatar_url=avatar_url, country_code=country_code, default_group=profile.default_group if profile else "default", is_active=profile.is_active if profile else True, @@ -253,13 +310,13 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio discord=profile.discord if profile else None, has_supported=profile.has_supported if profile else False, interests=profile.interests if profile else None, - join_date=db_user.join_date if db_user.join_date else None, + join_date=profile.join_date if profile else None, location=profile.location if profile else None, max_blocks=profile.max_blocks if profile and profile.max_blocks else 100, max_friends=profile.max_friends if profile and profile.max_friends else 500, post_count=profile.post_count if profile and profile.post_count else 0, profile_hue=profile.profile_hue if profile and profile.profile_hue else None, - profile_order=profile.profile_order if profile and profile.profile_order else ['me', 'recent_activity', 'top_ranks', 'medals', 'historical', 'beatmaps', 'kudosu'], + profile_order=profile_order, # 使用排序后的 profile_order title=profile.title if profile else None, title_url=profile.title_url if profile else None, twitter=profile.twitter if profile else None, @@ -271,7 +328,7 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio kudosu=kudosu, statistics=statistics, statistics_rulesets=statistics_rulesets, - beatmap_playcounts_count=db_user.beatmap_playcounts_count if db_user.beatmap_playcounts_count is not None else 0, + beatmap_playcounts_count=lzrcnt.beatmap_playcounts_count if lzrcnt else 0, comments_count=lzrcnt.comments_count if lzrcnt else 0, favourite_beatmapset_count=lzrcnt.favourite_beatmapset_count if lzrcnt else 0, follower_count=lzrcnt.follower_count if lzrcnt else 0, @@ -285,23 +342,23 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio ranked_and_approved_beatmapset_count=lzrcnt.ranked_and_approved_beatmapset_count if lzrcnt else 0, unranked_beatmapset_count=lzrcnt.unranked_beatmapset_count if lzrcnt else 0, scores_best_count=lzrcnt.scores_best_count if lzrcnt else 0, - scores_first_count=lzrcnt.socres_first_count if lzrcnt else 0, + scores_first_count=lzrcnt.scores_first_count if lzrcnt else 0, scores_pinned_count=lzrcnt.scores_pinned_count, - scores_recent_count=lzrcnt.recent_scores_count if lzrcnt else 0, - account_history=[], - active_tournament_banner=0, - active_tournament_banners=[], - badges=[], + scores_recent_count=lzrcnt.scores_recent_count if lzrcnt else 0, + account_history=[], #TODO: 获取用户历史账户信息 + active_tournament_banner=len(active_tournament_banners), + active_tournament_banners=active_tournament_banners, + badges=badges, current_season_stats=None, daily_challenge_user_stats=None, groups=[], - monthly_playcounts=[], + monthly_playcounts=monthly_playcounts, page=Page(html=profile.page_html, raw=profile.page_raw) if profile.page_html or profile.page_raw else Page(), - previous_usernames=[], + previous_usernames=previous_usernames, rank_highest=rank_highest, rank_history=rank_history, - rankHistory=rank_history, # 保留旧API兼容性字段 - replays_watched_counts=[], + rankHistory=rank_history, + replays_watched_counts=replays_watched_counts, team=team, user_achievements=user_achievements ) @@ -329,6 +386,7 @@ def get_country_name(country_code: str) -> str: def create_default_profile(db_user: DBUser): """创建默认的用户资料""" + # 完善 MockProfile 类定义 class MockProfile: def __init__(self): self.is_active = True @@ -342,7 +400,7 @@ def create_default_profile(db_user: DBUser): self.pm_friends_only = False self.default_group = 'default' self.last_visit = None - self.join_date = db_user.join_date + self.join_date = db_user.join_date if db_user else datetime.utcnow() self.profile_colour = None self.profile_hue = None self.avatar_url = None @@ -360,9 +418,15 @@ def create_default_profile(db_user: DBUser): self.max_blocks = 100 self.max_friends = 500 self.post_count = 0 + # 添加profile_order字段 + self.profile_order = MockLazerUserProfileSections.get_sorted_sections() self.page_html = None self.page_raw = None - + # 在MockProfile类中添加active_tournament_banners字段 + self.active_tournament_banners = MockLazerTournamentBanner.create_default_banners() + self.active_tournament_banners = [] # 默认空列表 + + return MockProfile() @@ -383,11 +447,11 @@ def create_default_lazer_statistics(mode: str): self.pp_exp = 0.0 self.ranked_score = 0 self.hit_accuracy = 0.0 - self.play_count = 0 - self.play_time = 0 self.total_score = 0 self.total_hits = 0 self.maximum_combo = 0 + self.play_count = 0 + self.play_time = 0 self.replays_watched_by_others = 0 self.is_ranked = False self.grade_ss = 0 @@ -427,8 +491,9 @@ def create_default_counts(): class MockCounts: def __init__(self): self.recent_scores_count = None - self.socres_first_count = None self.beatmap_playcounts_count = 0 + self.scores_first_count = 0 + self.scores_pinned_count = 0 self.comments_count = 0 self.favourite_beatmapset_count = 0 self.follower_count = 0 @@ -445,5 +510,52 @@ def create_default_counts(): self.scores_first_count = 0 self.scores_pinned_count = 0 self.scores_recent_count = 0 - return MockCounts() + + +class MockLazerUserProfileSections: + def __init__(self, section_name: str, display_order: int = 0): + self.section_name = section_name + self.display_order = display_order + + @staticmethod + def create_default_sections(): + """创建默认的用户资料板块配置""" + return [ + MockLazerUserProfileSections("me", 1), + MockLazerUserProfileSections("recent_activity", 2), + MockLazerUserProfileSections("top_ranks", 3), + MockLazerUserProfileSections("medals", 4), + MockLazerUserProfileSections("historical", 5), + MockLazerUserProfileSections("beatmaps", 6), + MockLazerUserProfileSections("kudosu", 7) + ] + + @staticmethod + def get_sorted_sections(sections=None): + """ + 对profile_order列表进行排序 + display_order = 0 的记录排在最后 + """ + if sections is None: + sections = MockLazerUserProfileSections.create_default_sections() + + return sorted( + sections, + key=lambda x: (x.display_order == 0, x.display_order) + ) + + +class MockLazerTournamentBanner: + def __init__(self, tournament_id: int, image_url: str, is_active: bool = True): + self.tournament_id = tournament_id + self.image_url = image_url + self.is_active = is_active + + @staticmethod + def create_default_banners(): + """创建默认的锦标赛横幅配置""" + return [ + MockLazerTournamentBanner(1, "https://example.com/banner1.jpg", True), + MockLazerTournamentBanner(2, "https://example.com/banner2.jpg", False) + ] From 451ef2f1d2cdb441a5de361c20aafbb9b9b31f60 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 19 Jul 2025 21:33:11 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E9=87=8D=E6=9E=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=95=B0=E6=8D=AE=E5=BA=93=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=92=8C=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新了 User 类中的多个关系属性,修正了部分属性的拼写错误和关联关系 - 修改了 LazerUserProfileSections 类的关联关系 - 修正了 LazerUserBanners 类的结构和关联关系 - 更新了 create_sample_data.py 中的统计类引用 - 在 config.py 中更新了数据库连接 URL --- app/config.py | 2 +- app/database.py | 38 +++++++++++++++++++++++++++++--------- create_sample_data.py | 10 +++++----- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/app/config.py b/app/config.py index c1310e8..a4a4dfd 100644 --- a/app/config.py +++ b/app/config.py @@ -5,7 +5,7 @@ load_dotenv() class Settings: # 数据库设置 - DATABASE_URL: str = os.getenv("DATABASE_URL", "mysql+pymysql://root:password@localhost:3306/osu_api") + DATABASE_URL: str = os.getenv("DATABASE_URL", "mysql+pymysql://root:Chinabug610@localhost:3306/osu_api") REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0") # JWT 设置 diff --git a/app/database.py b/app/database.py index b6f6a86..96a48de 100644 --- a/app/database.py +++ b/app/database.py @@ -62,14 +62,28 @@ class User(Base): lazer_profile = relationship("LazerUserProfile", back_populates="user", uselist=False, cascade="all, delete-orphan") lazer_statistics = relationship("LazerUserStatistics", back_populates="user", cascade="all, delete-orphan") lazer_achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") - lazer_profile_sections=relationship("LazerUserProfileSection", back_populates="user", cascade="all, delete-orphan") + lazer_profile_sections = relationship( + "LazerUserProfileSections", # 修正类名拼写(添加s) + back_populates="user", + cascade="all, delete-orphan" + ) statistics = relationship("LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan") - achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan") + achievements = relationship( + "LazerUserAchievement", + back_populates="user", + cascade="all, delete-orphan", + overlaps="lazer_achievements" + ) team_membership = relationship("TeamMember", back_populates="user", cascade="all, delete-orphan") daily_challenge_stats = relationship("DailyChallengeStats", back_populates="user", uselist=False, cascade="all, delete-orphan") rank_history = relationship("RankHistory", back_populates="user", cascade="all, delete-orphan") avatar = relationship("UserAvatar", back_populates="user", primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)", uselist=False) - active_banners=relationship("LazerUserBanners",back_populates="user",cascade="all, delete-orphan") + active_banners = relationship( + "UserAvatar", # 原定义指向LazerUserBanners,实际应为UserAvatar + back_populates="user", + primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)", + uselist=False + ) lazer_badges = relationship("LazerUserBadge", back_populates="user", cascade="all, delete-orphan") lazer_monthly_playcounts = relationship("LazerUserMonthlyPlaycounts", back_populates="user", cascade="all, delete-orphan") lazer_previous_usernames = relationship("LazerUserPreviousUsername", back_populates="user", cascade="all, delete-orphan") @@ -132,6 +146,7 @@ class LazerUserProfile(Base): # 关联关系 user = relationship("User", back_populates="lazer_profile") + class LazerUserProfileSections(Base): __tablename__ = "lazer_user_profile_sections" @@ -143,7 +158,8 @@ class LazerUserProfileSections(Base): created_at=Column(DateTime, default=datetime.utcnow) updated_at=Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - user = relationship("User", back_populates="lazer_profile_order") + user = relationship("User", back_populates="lazer_profile_sections") + class LazerUserCountry(Base): __tablename__ = "lazer_user_countries" @@ -251,13 +267,17 @@ class LazerUserStatistics(Base): class LazerUserBanners(Base): __tablename__ = "lazer_user_tournament_banners" - id=Column(Integer,primary_key=True) + id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) - tournament_id=Column(Integer, nullable=False) - image_url = Column(VARCHAR(500),nullable=False) - is_active=Column(TINYINT(1)) + tournament_id = Column(Integer, nullable=False) + image_url = Column(VARCHAR(500), nullable=False) + is_active = Column(TINYINT(1)) - user=relationship("User", back_populates="lazer_active_banners") + # 修正user关系的back_populates值 + user = relationship( + "User", + back_populates="active_banners" # 改为实际存在的属性名 + ) class LazerUserAchievement(Base): diff --git a/create_sample_data.py b/create_sample_data.py index e6bf457..2ad12c8 100644 --- a/create_sample_data.py +++ b/create_sample_data.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta import time from sqlalchemy.orm import Session from app.dependencies import get_db, engine -from app.database import Base, User, UserStatistics, UserAchievement, DailyChallengeStats, RankHistory +from app.database import Base, User, LazerUserStatistics, UserAchievement, DailyChallengeStats, RankHistory from app.auth import get_password_hash # 创建所有表 @@ -134,7 +134,7 @@ def create_sample_user(): db.refresh(user) # 创建 osu! 模式统计 - osu_stats = UserStatistics( + osu_stats = LazerUserStatistics( user_id=user.id, mode="osu", count_100=276274, @@ -165,7 +165,7 @@ def create_sample_user(): ) # 创建 taiko 模式统计 - taiko_stats = UserStatistics( + taiko_stats = LazerUserStatistics( user_id=user.id, mode="taiko", count_100=160, @@ -188,7 +188,7 @@ def create_sample_user(): ) # 创建 fruits 模式统计 - fruits_stats = UserStatistics( + fruits_stats = LazerUserStatistics( user_id=user.id, mode="fruits", count_100=109, @@ -212,7 +212,7 @@ def create_sample_user(): ) # 创建 mania 模式统计 - mania_stats = UserStatistics( + mania_stats = LazerUserStatistics( user_id=user.id, mode="mania", count_100=7867,