310 lines
13 KiB
Python
310 lines
13 KiB
Python
# -*- coding = utf-8 -*-
|
||
# @Time : 2025/2/13 下午12:17
|
||
# @File : models.py
|
||
# @Software : PyCharm
|
||
# @Author : xingc
|
||
# @Desc :
|
||
import enum
|
||
import uuid
|
||
from datetime import datetime, date
|
||
from typing import List
|
||
|
||
import ujson
|
||
from sqlalchemy import (
|
||
Column, Integer, String, Text, Date,
|
||
ForeignKey, DECIMAL, CheckConstraint,
|
||
UniqueConstraint, Index, Sequence, JSON, Enum, Boolean, UUID, Table
|
||
)
|
||
from sqlalchemy.orm import (
|
||
DeclarativeBase, Mapped, mapped_column, relationship
|
||
)
|
||
from sqlalchemy.sql import func
|
||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||
|
||
from core.base import schema as base_schema
|
||
|
||
|
||
class BaseModel(DeclarativeBase, AsyncAttrs):
|
||
id: Mapped[int] = mapped_column(
|
||
Integer, primary_key=True, autoincrement=True, comment='自增id'
|
||
)
|
||
|
||
def dict(self, exclude: set = None):
|
||
data = {}
|
||
for column in self.__table__.columns:
|
||
if exclude is not None and column.name in exclude:
|
||
continue
|
||
|
||
value = getattr(self, column.name)
|
||
if isinstance(value, datetime):
|
||
data[column.name] = value.strftime('%Y-%m-%d %H:%M:%S')
|
||
elif isinstance(value, enum.Enum):
|
||
data[column.name] = value.value
|
||
else:
|
||
data[column.name] = value
|
||
return data
|
||
|
||
def json(self, exclude: set = None):
|
||
return ujson.dumps(self.dict(exclude=exclude))
|
||
|
||
|
||
class DateModel:
|
||
created_at: Mapped[datetime] = mapped_column(server_default=func.current_timestamp(), comment='创建时间')
|
||
updated_at: Mapped[datetime] = mapped_column(server_default=func.current_timestamp(),
|
||
onupdate=func.current_timestamp(), comment='更新时间')
|
||
|
||
|
||
class User(BaseModel, DateModel):
|
||
__tablename__ = 'system_users'
|
||
__table_args__ = (
|
||
Index('idx_wechat_openid', 'wechat_openid'),
|
||
UniqueConstraint('user_id', name='uniq_user_id'),
|
||
{'comment': '用户表'}
|
||
)
|
||
|
||
id = None
|
||
user_id: Mapped[int] = mapped_column(
|
||
Integer, Sequence('non_primary_id_seq', start=100000, increment=1), primary_key=True, autoincrement=True,
|
||
comment='用户id'
|
||
)
|
||
nickname: Mapped[str] = mapped_column(String(20), comment='昵称')
|
||
gender: Mapped[int] = mapped_column(Integer, nullable=True, comment='性别')
|
||
avatar_url: Mapped[str] = mapped_column(String(500), nullable=True, 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'
|
||
)
|
||
chats: Mapped[List['Chat']] = relationship(secondary='chat_participant', back_populates='users')
|
||
messages: Mapped[List['Message']] = relationship(back_populates='user')
|
||
read_statuses: Mapped[List['ReadStatus']] = relationship(back_populates='user')
|
||
|
||
def __str__(self):
|
||
return f'{self.user_id}'
|
||
|
||
def is_vip(self) -> bool:
|
||
return self.vip_expire_date >= datetime.now().date()
|
||
|
||
|
||
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'
|
||
)
|
||
|
||
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'<UserFollows(follower_id={self.follower_id}, followed_id={self.followed_id})>'
|
||
|
||
|
||
class Card(BaseModel, DateModel):
|
||
__tablename__ = 'system_cards'
|
||
__table_args__ = (
|
||
Index('idx_cert_number_category_source', 'cert_number', 'category', 'source'),
|
||
{'comment': '卡片表'}
|
||
)
|
||
|
||
cert_number: Mapped[str] = mapped_column(String(50), comment='评级卡号')
|
||
card_year: Mapped[str] = mapped_column(String(50), nullable=True, comment='卡片年份')
|
||
card_brand: Mapped[str] = mapped_column(String(255), 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=lambda: dict(), comment='卡片正、反图片(本地)')
|
||
card_spare_picture: Mapped[dict] = mapped_column(JSON, default=lambda: dict(), 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),
|
||
nullable=True, server_default=base_schema.CardCategoryType.OTHER.value, comment='卡片分类'
|
||
)
|
||
source: Mapped[str] = mapped_column(
|
||
Enum(base_schema.PlatformSourceType, inherit_schema=True),
|
||
comment='来源,PSA、BGS、CGC、GBTC、CCG、BCTC :采集 、OTHER:自定义'
|
||
)
|
||
diy_source: Mapped[str] = mapped_column(String(50), nullable=True, comment='自定义品牌名')
|
||
|
||
collections: Mapped[list['UserCollection']] = relationship(
|
||
back_populates='card',
|
||
cascade='all, delete-orphan'
|
||
)
|
||
|
||
|
||
class UserCollection(BaseModel, DateModel):
|
||
__tablename__ = 'system_user_collections'
|
||
__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'),
|
||
{'comment': '卡片收藏关联表'}
|
||
)
|
||
|
||
user_id: Mapped[int] = mapped_column(
|
||
Integer, ForeignKey('system_users.user_id', ondelete='CASCADE'),
|
||
nullable=False
|
||
)
|
||
card_id: Mapped[int] = mapped_column(
|
||
Integer, ForeignKey('system_cards.id', ondelete='CASCADE'),
|
||
nullable=False
|
||
)
|
||
purchase_price: Mapped[float] = mapped_column(
|
||
DECIMAL(10, 2), default=0
|
||
)
|
||
display_price: Mapped[float] = mapped_column(
|
||
DECIMAL(10, 2), default=0
|
||
)
|
||
displayed: Mapped[bool] = mapped_column(
|
||
Boolean, default=False
|
||
)
|
||
|
||
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('idx_cert_and_card_number_source', 'cert_number', 'source'),
|
||
{'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(
|
||
Enum(base_schema.SupportPlatformType, inherit_schema=True),
|
||
comment='数据来源:PSA、BGS、CGC、GBTC、CCG、BCTC'
|
||
)
|
||
|
||
|
||
class Product(BaseModel, DateModel):
|
||
__tablename__ = 'system_products'
|
||
__table_args__ = {'comment': '产品信息表'}
|
||
|
||
name: Mapped[str] = mapped_column(String(20), comment='产品名')
|
||
category: Mapped[str] = mapped_column(
|
||
Enum(base_schema.ProductCategory, inherit_schema=True), comment='商品分类, vip-会员服务 points-积分服务'
|
||
)
|
||
price: Mapped[int] = mapped_column(Integer, comment='价格,不带小数点 单位为分')
|
||
action: Mapped[str] = mapped_column(String(10), comment='付款后的动作,会员续期:vip_renewal 积分:recharge_points')
|
||
number: Mapped[int] = mapped_column(Integer, comment='执行动作的增量值')
|
||
description: Mapped[str] = mapped_column(Text, nullable=True, comment='商品描述')
|
||
|
||
|
||
class Order(BaseModel, DateModel):
|
||
__tablename__ = 'system_orders'
|
||
__table_args__ = (
|
||
Index('idx_order_on_order_id', 'order_id'),
|
||
{'comment': '产品订单表'}
|
||
)
|
||
|
||
order_id: Mapped[str] = mapped_column(String(32), comment='订单id')
|
||
transaction_id: Mapped[str] = mapped_column(String(32), nullable=True, comment='微信订单id')
|
||
user_id: Mapped[int] = mapped_column(Integer, comment='用户id')
|
||
product_id: Mapped[int] = mapped_column(Integer, comment='产品id')
|
||
total_fee: Mapped[int] = mapped_column(Integer, nullable=True, comment='发起的总金额,单位为分')
|
||
notify_total_fee: Mapped[int] = mapped_column(Integer, nullable=True, comment='回调成功支付的总金额,单位为分')
|
||
pay_url: Mapped[str] = mapped_column(String(255), nullable=True, comment='支付地址')
|
||
status: Mapped[str] = mapped_column(
|
||
Enum(base_schema.OrderStatusType, inherit_schema=True),
|
||
server_default=base_schema.OrderStatusType.INIT.value,
|
||
comment='订单状态,init:初始化 fail:失败 ok:完成 wait payment:待支付 cancel. 取消'
|
||
)
|
||
|
||
|
||
class Chat(BaseModel, DateModel):
|
||
__tablename__ = 'system_chats'
|
||
__table_args__ = (
|
||
Index('idx_chat_on_guid', 'guid'),
|
||
{'comment': '聊天表'}
|
||
)
|
||
|
||
guid: Mapped[uuid.UUID] = mapped_column(String(36), nullable=True, default=lambda: str(uuid.uuid4()))
|
||
|
||
users: Mapped[List['User']] = relationship(secondary='chat_participant', back_populates='chats')
|
||
messages: Mapped[List['Message']] = relationship(back_populates='chat', cascade='all,delete')
|
||
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
read_statuses: Mapped[List['ReadStatus']] = relationship(back_populates='chat', cascade='all,delete')
|
||
|
||
|
||
class Message(BaseModel, DateModel):
|
||
__tablename__ = 'system_messages'
|
||
__table_args__ = (
|
||
Index('idx_message_on_user_id', 'user_id'),
|
||
Index('idx_message_on_chat_id', 'chat_id'),
|
||
Index('idx_message_on_user_id_chat_id', 'chat_id', 'user_id'),
|
||
{'comment': '聊天消息表'}
|
||
)
|
||
|
||
guid: Mapped[uuid.UUID] = mapped_column(String(36), nullable=True, default=lambda: str(uuid.uuid4()))
|
||
user_id: Mapped[int] = mapped_column(ForeignKey('system_users.user_id'))
|
||
chat_id: Mapped[int] = mapped_column(ForeignKey('system_chats.id'))
|
||
|
||
message_type: Mapped[str] = mapped_column(
|
||
Enum(base_schema.MessageType, inherit_schema=True), default=base_schema.MessageType.TEXT
|
||
)
|
||
content: Mapped[str] = mapped_column(String(5000), nullable=True, comment='文本消息')
|
||
|
||
chat: Mapped['Chat'] = relationship(back_populates='messages')
|
||
user: Mapped['User'] = relationship(back_populates='messages')
|
||
|
||
def __str__(self):
|
||
return f'{self.content}'
|
||
|
||
|
||
class ReadStatus(BaseModel, DateModel):
|
||
__tablename__ = 'system_read_status'
|
||
__table_args__ = (
|
||
Index('idx_read_status_on_chat_id', 'chat_id'),
|
||
Index('idx_read_status_on_user_id', 'user_id'),
|
||
{'comment': '聊天消息读取状态表'}
|
||
)
|
||
|
||
last_read_message_id: Mapped[int] = mapped_column(nullable=True)
|
||
user_id: Mapped[int] = mapped_column(ForeignKey('system_users.user_id'))
|
||
chat_id: Mapped[int] = mapped_column(ForeignKey('system_chats.id'))
|
||
|
||
chat: Mapped['Chat'] = relationship(back_populates='read_statuses')
|
||
user: Mapped['User'] = relationship(back_populates='read_statuses')
|
||
|
||
def __str__(self):
|
||
return f'User: {self.user_id}, Message: {self.last_read_message_id}'
|
||
|
||
|
||
chat_participant = Table(
|
||
'chat_participant',
|
||
BaseModel.metadata,
|
||
Column('user_id', Integer, ForeignKey('system_users.user_id'), primary_key=True),
|
||
Column('chat_id', Integer, ForeignKey('system_chats.id'), primary_key=True),
|
||
comment='聊天参与者关联表'
|
||
)
|