diff --git a/alembic/versions/4c950125b0a6_init.py b/alembic/versions/b2d35cbc76bd_init.py similarity index 67% rename from alembic/versions/4c950125b0a6_init.py rename to alembic/versions/b2d35cbc76bd_init.py index 1a4baec..bd8baef 100644 --- a/alembic/versions/4c950125b0a6_init.py +++ b/alembic/versions/b2d35cbc76bd_init.py @@ -1,8 +1,8 @@ """Init -Revision ID: 4c950125b0a6 +Revision ID: b2d35cbc76bd Revises: -Create Date: 2025-02-17 03:17:02.044360 +Create Date: 2025-02-21 00:59:31.520384 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = '4c950125b0a6' +revision: str = 'b2d35cbc76bd' down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -22,7 +22,6 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table('system_cache_cards', sa.Column('cert_number', sa.String(length=50), nullable=False, comment='评级卡号'), - sa.Column('card_number', sa.String(length=50), nullable=False, comment='卡片编号'), sa.Column('data', sa.JSON(), nullable=False, comment='标准数据'), sa.Column('raw_data', sa.JSON(), nullable=False, comment='原始数据'), sa.Column('source', sa.Enum('PSA', 'BGS', 'CGC', 'GBTC', 'CCG', 'BCTC', name='supportplatformtype', inherit_schema=True), nullable=False, comment='数据来源:PSA、BGS、CGC、GBTC、CCG、BCTC'), @@ -32,20 +31,21 @@ def upgrade() -> None: sa.PrimaryKeyConstraint('id'), comment='搜索卡片信息缓存表' ) - op.create_index('cert_and_card_number_source', 'system_cache_cards', ['cert_number', 'card_number', 'source'], unique=False) + op.create_index('cert_and_card_number_source', 'system_cache_cards', ['cert_number', 'source'], unique=False) op.create_table('system_cards', sa.Column('cert_number', sa.String(length=50), nullable=False, comment='评级卡号'), - sa.Column('card_year', sa.Integer(), nullable=False, comment='卡片年份'), - sa.Column('card_brand', sa.Integer(), nullable=False, comment='卡片品牌'), - sa.Column('card_score', sa.DECIMAL(precision=10, scale=1), nullable=False, comment='卡片评分'), - sa.Column('auto_score', sa.DECIMAL(precision=10, scale=1), nullable=False, comment='签字评分'), - sa.Column('card_name', sa.String(length=225), nullable=False, comment='卡片名称'), - sa.Column('card_number', sa.String(length=225), nullable=False, comment='卡片编号'), - sa.Column('card_type', sa.String(length=20), nullable=False, comment='卡片类型'), + sa.Column('card_year', sa.String(length=50), nullable=True, comment='卡片年份'), + sa.Column('card_brand', sa.String(length=50), nullable=True, comment='卡片品牌'), + sa.Column('card_score', sa.DECIMAL(precision=10, scale=1), nullable=True, comment='卡片评分'), + sa.Column('auto_score', sa.DECIMAL(precision=10, scale=1), nullable=True, comment='签字评分'), + sa.Column('card_name', sa.String(length=225), nullable=True, comment='卡片名称'), + sa.Column('card_number', sa.String(length=225), nullable=True, comment='卡片编号'), + sa.Column('card_type', sa.String(length=20), nullable=True, comment='卡片类型'), sa.Column('card_picture', sa.JSON(), nullable=False, comment='卡片正、反图片(本地)'), sa.Column('card_spare_picture', sa.JSON(), nullable=False, comment='卡片正、反图片(远程)'), - sa.Column('create_user_id', sa.Integer(), nullable=False, comment='创建者'), - sa.Column('category', sa.Enum('SPORTS', 'ANIME', 'OTHER', name='cardcategorytype', inherit_schema=True), nullable=False, comment='卡片分类'), + sa.Column('create_user_id', sa.Integer(), nullable=True, comment='创建者'), + sa.Column('perfect_user_id', sa.Integer(), nullable=True, comment='完善者'), + sa.Column('category', sa.Enum('SPORTS', 'ANIME', 'OTHER', name='cardcategorytype', inherit_schema=True), nullable=True, comment='卡片分类'), sa.Column('source', sa.Enum('PSA', 'BGS', 'CGC', 'GBTC', 'CCG', 'BCTC', 'OTHER', name='platformsourcetype', inherit_schema=True), nullable=False, comment='来源,PSA、BGS、CGC、GBTC、CCG、BCTC :采集 、OTHER:自定义'), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='自增id'), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'), @@ -55,45 +55,56 @@ def upgrade() -> None: ) op.create_index('cert_number_category_source', 'system_cards', ['cert_number', 'category', 'source'], unique=False) op.create_table('system_users', + sa.Column('user_id', sa.Integer(), autoincrement=True, nullable=False, comment='用户id'), sa.Column('nickname', sa.String(length=20), nullable=False, comment='昵称'), - sa.Column('user_id', sa.Integer(), nullable=False, comment='用户id'), - sa.Column('head_avatar', sa.String(length=500), nullable=False, comment='头像链接'), - sa.Column('wechat_openid', sa.String(length=100), nullable=True, comment='微信openid'), + sa.Column('gender', sa.Integer(), nullable=False, comment='性别'), + sa.Column('avatar_url', sa.String(length=500), nullable=False, comment='头像链接'), + sa.Column('wechat_openid', sa.String(length=100), nullable=False, comment='微信openid'), sa.Column('wechat_union_id', sa.String(length=100), nullable=True, comment='微信unionid'), - sa.Column('phone', sa.Integer(), nullable=False, comment='手机号码'), + sa.Column('phone', sa.String(length=11), nullable=True, comment='手机号码'), sa.Column('vip_expire_date', sa.Date(), nullable=False, comment='会员到期时间'), - sa.Column('integral', sa.Integer(), nullable=False, comment='会员积分'), - sa.Column('card_total', sa.Integer(), nullable=True, comment='卡牌总数'), - sa.Column('card_show_total', sa.Integer(), nullable=True, comment='卡片展示总数'), - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='自增id'), + sa.Column('points', sa.Integer(), nullable=False, comment='会员积分'), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'), sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='更新时间'), - sa.PrimaryKeyConstraint('id'), + sa.PrimaryKeyConstraint('user_id'), sa.UniqueConstraint('user_id', name='uniq_user_id'), comment='用户表' ) + op.create_index('wechat_openid', 'system_users', ['wechat_openid'], unique=False) op.create_table('system_user_collections', sa.Column('user_id', sa.Integer(), nullable=False), sa.Column('card_id', sa.Integer(), nullable=False), sa.Column('purchase_price', sa.DECIMAL(precision=10, scale=2), nullable=False), sa.Column('display_price', sa.DECIMAL(precision=10, scale=2), nullable=False), sa.Column('displayed', sa.Boolean(), nullable=False), - sa.Column('updated_at', sa.Boolean(), nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='自增id'), sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'), sa.CheckConstraint('display_value >= 0', name='check_display_price'), sa.CheckConstraint('purchase_price >= 0', name='check_purchase_price'), sa.ForeignKeyConstraint(['card_id'], ['system_cards.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['user_id'], ['system_users.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['system_users.user_id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('user_id', 'card_id', name='uniq_user_card') + sa.UniqueConstraint('user_id', 'card_id', name='uniq_user_card'), + comment='卡片收藏关联表' + ) + op.create_table('system_user_followers', + sa.Column('follower_id', sa.Integer(), nullable=False, comment='关注者的用户id'), + sa.Column('followed_id', sa.Integer(), nullable=False, comment='被关注者的用户id'), + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='自增id'), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'), + sa.ForeignKeyConstraint(['followed_id'], ['system_users.user_id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['follower_id'], ['system_users.user_id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + comment='用户关注关系表' ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('system_user_followers') op.drop_table('system_user_collections') + op.drop_index('wechat_openid', table_name='system_users') op.drop_table('system_users') op.drop_index('cert_number_category_source', table_name='system_cards') op.drop_table('system_cards') diff --git a/core/db/crud.py b/core/db/crud.py index 9de6cea..72e9782 100644 --- a/core/db/crud.py +++ b/core/db/crud.py @@ -32,7 +32,7 @@ class CRUD: async def get_paginated(self, filters: dict, page: int = 1, page_size: int = 10) -> Tuple[List[Model], int]: async with self.session.begin(): filter_conditions = self.build_filter_conditions(filters) if filters else [] - query = select(self.model).filter(*filter_conditions).order_by(desc(self.model.insert_time)) + query = select(self.model).filter(*filter_conditions).order_by(desc(self.model.created_at)) total_query = select(func.count()).select_from(self.model).filter(*filter_conditions) total_result = await self.session.execute(total_query) @@ -45,16 +45,10 @@ class CRUD: return records, total async def get_grouped_count(self, group_by_field: str, filters: Dict[str, Any] = None) -> Dict[Any, int]: - # 构建过滤条件 filter_conditions = self.build_filter_conditions(filters) if filters else [] - - # 确定分组字段 group_by = getattr(self.model, group_by_field) - - # 构建查询 query = select(group_by, func.count()).filter(*filter_conditions).group_by(group_by) - # 执行查询并获取结果 async with self.session.begin(): result = await self.session.execute(query) records = result.all() @@ -98,7 +92,7 @@ class CRUD: if any([ value is None, value == getattr(record, key), - key in ['insert_time', 'update_time'] + key in ['created_at', 'updated_at'] ]): continue setattr(record, key, value) diff --git a/core/db/models.py b/core/db/models.py index c8e1bf7..5b29de9 100644 --- a/core/db/models.py +++ b/core/db/models.py @@ -5,8 +5,9 @@ # @Author : xingc # @Desc : import enum +import random import uuid -from datetime import datetime +from datetime import datetime, date import ujson from sqlalchemy import ( @@ -56,27 +57,61 @@ class DateModel: class User(BaseModel, DateModel): __tablename__ = 'system_users' __table_args__ = ( + Index('wechat_openid', 'wechat_openid'), UniqueConstraint('user_id', name='uniq_user_id'), {'comment': '用户表'} ) - - nickname: Mapped[str] = mapped_column(String(20), nullable=False, comment='昵称') + id = None user_id: Mapped[int] = mapped_column( - Integer, Sequence('non_primary_id_seq', start=100000, increment=2), comment='用户id' + Integer, Sequence('non_primary_id_seq', start=100000, increment=1), primary_key=True, autoincrement=True, + comment='用户id' ) - head_avatar: Mapped[str] = mapped_column(String(500), comment='头像链接') - wechat_openid = mapped_column(String(100), comment='微信openid') - wechat_union_id = mapped_column(String(100), comment='微信unionid') - phone: Mapped[int] = mapped_column(Integer, comment='手机号码') - vip_expire_date: Mapped[datetime] = mapped_column(Date, comment='会员到期时间') - integral: Mapped[int] = mapped_column(Integer, default=0, comment='会员积分') - card_total: Mapped[int] = Column(Integer, default=0, comment='卡牌总数') - card_show_total: Mapped[int] = Column(Integer, default=0, comment='卡片展示总数') + nickname: Mapped[str] = mapped_column(String(20), comment='昵称') + gender: Mapped[int] = mapped_column(Integer, comment='性别') + avatar_url: Mapped[str] = mapped_column(String(500), comment='头像链接') + wechat_openid: Mapped[str] = mapped_column(String(100), comment='微信openid') + wechat_union_id: Mapped[str] = mapped_column(String(100), nullable=True, comment='微信unionid') + phone: Mapped[str] = mapped_column(String(11), nullable=True, comment='手机号码') + vip_expire_date: Mapped[date] = mapped_column(Date, default=datetime.now().date, comment='会员到期时间') + points: Mapped[int] = mapped_column(Integer, default=0, comment='会员积分') collections: Mapped[list["UserCollection"]] = relationship( back_populates="user", cascade="all, delete-orphan" ) + # 关注者 + following: Mapped[list["UserFollows"]] = relationship( + 'UserFollows', + foreign_keys='UserFollows.follower_id', + cascade="all, delete-orphan" + ) + # 定义被关注关系 + followers: Mapped[list["UserFollows"]] = relationship( + 'UserFollows', + foreign_keys='UserFollows.followed_id', + cascade="all, delete-orphan" + ) + + +class UserFollows(BaseModel, DateModel): + __tablename__ = 'system_user_followers' + __table_args__ = ( + {'comment': '用户关注关系表'} + ) + + follower_id: Mapped[int] = mapped_column( + Integer, ForeignKey('system_users.user_id', ondelete="CASCADE"), comment='关注者的用户id' + ) + followed_id: Mapped[int] = mapped_column( + Integer, ForeignKey('system_users.user_id', ondelete="CASCADE"), comment='被关注者的用户id' + ) + updated_at = None + + follower = relationship('User', foreign_keys=[follower_id], back_populates='following') + followed = relationship('User', foreign_keys=[followed_id], back_populates='followers') + + def __repr__(self): + return f"" class Card(BaseModel, DateModel): @@ -87,20 +122,21 @@ class Card(BaseModel, DateModel): ) cert_number: Mapped[str] = mapped_column(String(50), comment='评级卡号') - card_year: Mapped[int] = mapped_column(Integer, comment='卡片年份') - card_brand: Mapped[str] = mapped_column(Integer, comment='卡片品牌') - card_score: Mapped[float] = mapped_column(DECIMAL(10, 1), comment='卡片评分') - auto_score: Mapped[float] = mapped_column(DECIMAL(10, 1), comment='签字评分') - card_name: Mapped[str] = mapped_column(String(225), comment='卡片名称') - card_number: Mapped[str] = mapped_column(String(225), comment='卡片编号') - card_type: Mapped[str] = mapped_column(String(20), comment='卡片类型') + card_year: Mapped[str] = mapped_column(String(50), nullable=True, comment='卡片年份') + card_brand: Mapped[str] = mapped_column(String(50), nullable=True, comment='卡片品牌') + card_score: Mapped[float] = mapped_column(DECIMAL(10, 1), nullable=True, comment='卡片评分') + auto_score: Mapped[float] = mapped_column(DECIMAL(10, 1), nullable=True, comment='签字评分') + card_name: Mapped[str] = mapped_column(String(225), nullable=True, comment='卡片名称') + card_number: Mapped[str] = mapped_column(String(225), nullable=True, comment='卡片编号') + card_type: Mapped[str] = mapped_column(String(20), nullable=True, comment='卡片类型') card_picture: Mapped[dict] = mapped_column(JSON, default=dict, comment='卡片正、反图片(本地)') card_spare_picture: Mapped[dict] = mapped_column(JSON, default=dict, comment='卡片正、反图片(远程)') - create_user_id: Mapped[int] = mapped_column(Integer, comment='创建者') + create_user_id: Mapped[int] = mapped_column(Integer, nullable=True, comment='创建者') + perfect_user_id: Mapped[int] = mapped_column(Integer, nullable=True, comment='完善者') category: Mapped[str] = mapped_column( Enum(base_schema.CardCategoryType, inherit_schema=True), - default=base_schema.CardCategoryType.OTHER, comment='卡片分类' + nullable=True, default=base_schema.CardCategoryType.OTHER, comment='卡片分类' ) source: Mapped[str] = mapped_column( Enum(base_schema.PlatformSourceType, inherit_schema=True), @@ -118,41 +154,41 @@ class UserCollection(BaseModel, DateModel): __table_args__ = ( UniqueConstraint('user_id', 'card_id', name='uniq_user_card'), CheckConstraint('purchase_price >= 0', name='check_purchase_price'), - CheckConstraint('display_value >= 0', name='check_display_price') + CheckConstraint('display_value >= 0', name='check_display_price'), + {'comment': '卡片收藏关联表'} ) user_id: Mapped[int] = mapped_column( - Integer, ForeignKey("system_users.id", ondelete="CASCADE"), + Integer, ForeignKey('system_users.user_id', ondelete='CASCADE'), nullable=False ) card_id: Mapped[int] = mapped_column( - Integer, ForeignKey("system_cards.id", ondelete="CASCADE"), + Integer, ForeignKey('system_cards.id', ondelete='CASCADE'), nullable=False ) purchase_price: Mapped[float] = mapped_column( - DECIMAL(10, 2), nullable=False + DECIMAL(10, 2), default=0 ) display_price: Mapped[float] = mapped_column( - DECIMAL(10, 2), nullable=False + DECIMAL(10, 2), default=0 ) displayed: Mapped[bool] = mapped_column( Boolean, default=True ) - updated_at: Mapped[bool] = None + updated_at = None - user: Mapped["User"] = relationship(back_populates="collections") - card: Mapped["Card"] = relationship(back_populates="collections") + user: Mapped["User"] = relationship(back_populates='collections') + card: Mapped["Card"] = relationship(back_populates='collections') class SearchCardCache(BaseModel, DateModel): __tablename__ = 'system_cache_cards' __table_args__ = ( - Index('cert_and_card_number_source', 'cert_number', 'card_number', 'source'), + Index('cert_and_card_number_source', 'cert_number', 'source'), {'comment': '搜索卡片信息缓存表'} ) - cert_number: Mapped[str] = mapped_column(String(50), comment='评级卡号') - card_number: Mapped[str] = mapped_column(String(50), comment='卡片编号') + cert_number: Mapped[str] = mapped_column(String(50), nullable=False, comment='评级卡号') data: Mapped[dict] = mapped_column(JSON, comment='标准数据') raw_data: Mapped[dict] = mapped_column(JSON, comment='原始数据') source: Mapped[str] = mapped_column(