add banban service code

This commit is contained in:
HycJack
2026-04-27 18:40:20 +08:00
parent 4ceafa9059
commit e9a69577db
39 changed files with 4952 additions and 85 deletions

View File

@@ -1,71 +1,121 @@
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from config import settings
from utils.logger import session_logger
import asyncio
import urllib.parse
def _build_connection_string() -> str:
encoded_password = urllib.parse.quote_plus(settings.db_password)
return f"mysql+aiomysql://{settings.db_user}:{encoded_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}?charset=utf8mb4"
def _build_sync_connection_string() -> str:
encoded_password = urllib.parse.quote_plus(settings.db_password)
return f"mysql+pymysql://{settings.db_user}:{encoded_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}?charset=utf8mb4"
class DatabaseManager:
_instance = None
_lock = asyncio.Lock()
def __init__(self):
self.engine = None
self.async_engine = None
self.sync_engine = None
self.async_session = None
self.sync_session_maker = None
self._initialized = False
@classmethod
async def get_instance(cls):
async with cls._lock:
if cls._instance is None:
cls._instance = DatabaseManager()
if not cls._instance._initialized:
await cls._instance.initialize()
return cls._instance
async def initialize(self):
if self._initialized:
return
try:
encoded_password = urllib.parse.quote_plus(settings.db_password)
connection_string = f"mysql+aiomysql://{settings.db_user}:{encoded_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}?charset=utf8mb4"
self.engine = create_async_engine(
connection_string = _build_connection_string()
self.async_engine = create_async_engine(
connection_string,
echo=settings.db_echo,
pool_pre_ping=True,
pool_recycle=3600,
)
self.async_session = sessionmaker(
self.engine, expire_on_commit=False, class_=AsyncSession
self.async_engine, expire_on_commit=False, class_=AsyncSession
)
sync_connection_string = _build_sync_connection_string()
self.sync_engine = create_engine(
sync_connection_string,
echo=settings.db_echo,
pool_pre_ping=True,
pool_recycle=3600,
)
self.sync_session_maker = sessionmaker(
bind=self.sync_engine, expire_on_commit=False
)
self._initialized = True
session_logger.info("system", "database", f"数据库连接初始化成功: {settings.db_host}:{settings.db_port}/{settings.db_name}")
except Exception as e:
session_logger.error("system", "database", f"数据库连接初始化失败: {str(e)}")
raise
async def get_session(self):
"""获取数据库会话"""
"""获取异步数据库会话"""
if not self._initialized:
await self.initialize()
return self.async_session()
def get_sync_session(self):
"""获取同步数据库会话"""
if not self._initialized:
raise RuntimeError("DatabaseManager not initialized. Call get_instance() first.")
return self.sync_session_maker()
async def close(self):
"""关闭数据库连接"""
if self.engine:
await self.engine.dispose()
self._initialized = False
session_logger.info("system", "database", "数据库连接已关闭")
if self.async_engine:
await self.async_engine.dispose()
if self.sync_engine:
self.sync_engine.dispose()
self._initialized = False
session_logger.info("system", "database", "数据库连接已关闭")
db_manager = None
async def get_db_manager():
global db_manager
if db_manager is None:
db_manager = await DatabaseManager.get_instance()
return db_manager
def get_sync_session_maker():
"""获取同步 session maker同步版本优先复用已有 manager"""
global db_manager
if db_manager is not None and db_manager._initialized:
return db_manager.sync_session_maker
sync_engine = create_engine(
_build_sync_connection_string(),
echo=settings.db_echo,
pool_pre_ping=True,
pool_recycle=3600,
)
return sessionmaker(bind=sync_engine, expire_on_commit=False)

View File

@@ -1,9 +1,11 @@
from sqlalchemy import Column, String, Text, Float, DateTime, Integer, Boolean, func, ForeignKey, JSON
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import UniqueConstraint
import datetime
from typing import Optional
from sqlalchemy import Column, String, Text, Float, DateTime, Integer, Boolean, func, ForeignKey, JSON, UniqueConstraint, BigInteger, Numeric, SmallInteger, Date, Index, Time, text
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
Base = declarative_base()
from datetime import date, time, datetime
class Base(DeclarativeBase):
pass
class DeviceConfig(Base):
__tablename__ = "device_configs"
@@ -13,7 +15,7 @@ class DeviceConfig(Base):
selected_role_key = Column(String(64), nullable=False)
preferred_language = Column(String(10), nullable=True)
volume = Column(Integer, nullable=True)
last_update_time = Column(Float, nullable=False, default=lambda: datetime.datetime.now().timestamp())
last_update_time = Column(Float, nullable=False, default=lambda: datetime.now().timestamp())
created_at = Column(DateTime, nullable=False, server_default=func.now())
updated_at = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
@@ -30,7 +32,7 @@ class ConversationHistory(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
device_id = Column(String(64), nullable=False, index=True)
role_key = Column(String(64), nullable=False, index=True)
last_interaction_time = Column(Float, nullable=False, default=lambda: datetime.datetime.now().timestamp())
last_interaction_time = Column(Float, nullable=False, default=lambda: datetime.now().timestamp())
created_at = Column(DateTime, nullable=False, server_default=func.now())
updated_at = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
__table_args__ = (
@@ -43,7 +45,7 @@ class ConversationMessage(Base):
conversation_id = Column(Integer, ForeignKey("conversation_histories.id", ondelete="CASCADE"), nullable=False, index=True)
is_user = Column(Boolean, nullable=False, default=False) # True表示用户消息False表示助手消息
content = Column(Text, nullable=False)
timestamp = Column(Float, nullable=False, default=lambda: datetime.datetime.now().timestamp())
timestamp = Column(Float, nullable=False, default=lambda: datetime.now().timestamp())
created_at = Column(DateTime, nullable=False, server_default=func.now())
updated_at = Column(DateTime, nullable=False, server_default=func.now(), onupdate=func.now())
__table_args__ = (
@@ -129,7 +131,7 @@ class SystemConfig(Base):
class Card(Base):
__tablename__ = "cards"
card_id = Column(Integer, primary_key=True, autoincrement=True)
card_uuid = Column(String(64), unique=True, nullable=False)
device_id = Column(String(64), index=True)
@@ -138,7 +140,232 @@ class Card(Base):
total_swaps = Column(Integer, server_default="0")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
__table_args__ = (
{'mysql_charset': 'utf8mb4', 'mysql_collate': 'utf8mb4_unicode_ci'}
)
class Parent(Base):
__tablename__ = "parents"
user_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
openid: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
unionid: Mapped[Optional[str]] = mapped_column(String(64))
nickname: Mapped[Optional[str]] = mapped_column(String(64))
avatar_url: Mapped[Optional[str]] = mapped_column(String(255))
avatar_file_key: Mapped[Optional[str]] = mapped_column(String(255))
phone: Mapped[Optional[str]] = mapped_column(String(20))
status: Mapped[int] = mapped_column(Integer, server_default="1")
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
class Child(Base):
__tablename__ = "children"
child_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
child_name: Mapped[str] = mapped_column(String(32), nullable=False)
child_gender: Mapped[int] = mapped_column(Integer, server_default="2")
child_birthday: Mapped[Optional[date]] = mapped_column(Date)
status: Mapped[int] = mapped_column(Integer, server_default="1")
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
class ParentChildRelation(Base):
__tablename__ = "parent_child_relations"
__table_args__ = (
UniqueConstraint("user_id", "child_id", name="uq_user_child"),
Index("idx_pcr_user_id", "user_id"),
Index("idx_pcr_child_id", "child_id"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
child_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
relation_type: Mapped[int] = mapped_column(Integer, server_default="9")
is_primary: Mapped[bool] = mapped_column(Boolean, server_default="0")
status: Mapped[int] = mapped_column(Integer, server_default="1")
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
class DeviceBinding(Base):
__tablename__ = "device_bindings"
__table_args__ = (
UniqueConstraint("device_id", name="uq_device_binding_device"),
UniqueConstraint("child_id", name="uq_device_binding_child"),
Index("idx_device_bindings_owner_user_id", "owner_user_id"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
device_id: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
owner_user_id: Mapped[int] = mapped_column(Integer, nullable=False)
child_id: Mapped[Optional[int]] = mapped_column(Integer)
status: Mapped[int] = mapped_column(Integer, server_default="1")
bound_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
unbound_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
class DeviceBindSession(Base):
__tablename__ = "device_bind_sessions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
bind_token: Mapped[str] = mapped_column(String(36), unique=True, nullable=False)
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
initiator_user_id: Mapped[int] = mapped_column(Integer, nullable=False)
target_child_id: Mapped[Optional[int]] = mapped_column(Integer)
challenge_code_hash: Mapped[Optional[str]] = mapped_column(String(64))
challenge_set_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
max_attempt_count: Mapped[int] = mapped_column(Integer, server_default="5")
attempt_count: Mapped[int] = mapped_column(Integer, server_default="0")
status: Mapped[int] = mapped_column(Integer, server_default="1")
confirmed_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
consumed_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
class DeviceBindHistory(Base):
__tablename__ = "device_bind_history"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
child_id: Mapped[Optional[int]] = mapped_column(Integer)
bound_by_user_id: Mapped[int] = mapped_column(Integer, nullable=False)
unbound_by_user_id: Mapped[Optional[int]] = mapped_column(Integer)
bind_source: Mapped[int] = mapped_column(Integer, server_default="1")
bound_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
unbound_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
unbind_reason: Mapped[Optional[str]] = mapped_column(String(191))
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
class DeviceSetting(Base):
__tablename__ = "device_settings"
setting_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
device_id: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
sleep_mode: Mapped[int] = mapped_column(Integer, server_default="0")
disable_time_start: Mapped[Optional[time]] = mapped_column(Time)
disable_time_end: Mapped[Optional[time]] = mapped_column(Time)
timezone: Mapped[str] = mapped_column(String(32), server_default=text("'Asia/Shanghai'"))
volume: Mapped[Optional[int]] = mapped_column(Integer)
brightness: Mapped[Optional[int]] = mapped_column(Integer)
disable_weekdays: Mapped[Optional[str]] = mapped_column(String(32))
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
updated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime,
server_default=text("CURRENT_TIMESTAMP"),
onupdate=datetime.utcnow,
)
class IMConversation(Base):
__tablename__ = "im_conversations"
__table_args__ = (
UniqueConstraint("conversation_type", "pair_key", name="uq_conv_type_pair"),
Index("idx_im_conv_participant_a", "participant_a_type", "participant_a_id"),
Index("idx_im_conv_participant_b", "participant_b_type", "participant_b_id"),
Index("idx_im_conv_last_message_at", "last_message_at"),
)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
conversation_type: Mapped[int] = mapped_column(Integer, nullable=False)
participant_a_type: Mapped[int] = mapped_column(Integer, nullable=False)
participant_a_id: Mapped[str] = mapped_column(String(64), nullable=False)
participant_b_type: Mapped[int] = mapped_column(Integer, nullable=False)
participant_b_id: Mapped[str] = mapped_column(String(64), nullable=False)
pair_key: Mapped[str] = mapped_column(String(191), nullable=False)
status: Mapped[int] = mapped_column(Integer, default=1)
last_seq: Mapped[int] = mapped_column(BigInteger, default=0, index=True)
message_count: Mapped[int] = mapped_column(BigInteger, default=0)
last_message_preview: Mapped[Optional[str]] = mapped_column(String(255))
last_message_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
ext_json: Mapped[Optional[dict]] = mapped_column(JSON)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
)
class IMMessage(Base):
__tablename__ = "im_messages"
__table_args__ = (
UniqueConstraint("conversation_id", "seq", name="uq_im_msg_conv_seq"),
UniqueConstraint("conversation_id", "client_msg_id", name="uq_im_msg_client"),
Index("idx_im_msg_sender", "sender_type", "sender_id", "created_at"),
Index("idx_im_msg_receiver", "receiver_type", "receiver_id", "created_at"),
)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
conversation_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
seq: Mapped[int] = mapped_column(BigInteger, nullable=False)
sender_type: Mapped[int] = mapped_column(Integer, nullable=False)
sender_id: Mapped[str] = mapped_column(String(64), nullable=False)
receiver_type: Mapped[int] = mapped_column(Integer, nullable=False)
receiver_id: Mapped[str] = mapped_column(String(64), nullable=False)
content_type: Mapped[int] = mapped_column(Integer, nullable=False)
content_text: Mapped[Optional[str]] = mapped_column(Text)
content_json: Mapped[Optional[dict]] = mapped_column(JSON)
media_file_key: Mapped[Optional[str]] = mapped_column(String(255))
media_duration_ms: Mapped[Optional[int]] = mapped_column(Integer)
media_mime_type: Mapped[Optional[str]] = mapped_column(String(64))
media_size_bytes: Mapped[Optional[int]] = mapped_column(BigInteger)
media_transcript_text: Mapped[Optional[str]] = mapped_column(Text)
client_msg_id: Mapped[Optional[str]] = mapped_column(String(64))
sender_name_snapshot: Mapped[Optional[str]] = mapped_column(String(64))
sender_avatar_snapshot: Mapped[Optional[str]] = mapped_column(String(255))
receiver_name_snapshot: Mapped[Optional[str]] = mapped_column(String(64))
receiver_avatar_snapshot: Mapped[Optional[str]] = mapped_column(String(255))
ext_json: Mapped[Optional[dict]] = mapped_column(JSON)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
class ChildLocationCurrent(Base):
__tablename__ = "child_location_current"
child_id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
coord_type: Mapped[str] = mapped_column(String(16), default="gcj02")
lat: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
lng: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
accuracy_m: Mapped[Optional[int]] = mapped_column(Integer)
altitude_m: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
speed_mps: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
heading_deg: Mapped[Optional[int]] = mapped_column(SmallInteger)
source: Mapped[int] = mapped_column(Integer, nullable=False)
battery_pct: Mapped[Optional[int]] = mapped_column(Integer)
device_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
server_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
)
class ChildLocationHistory(Base):
__tablename__ = "child_location_history"
__table_args__ = (
Index("idx_child_loc_hist_child", "child_id", "created_at"),
)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
child_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
device_id: Mapped[str] = mapped_column(String(64), nullable=False)
coord_type: Mapped[str] = mapped_column(String(16), default="gcj02")
lat: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
lng: Mapped[float] = mapped_column(Numeric(10, 7), nullable=False)
accuracy_m: Mapped[Optional[int]] = mapped_column(Integer)
altitude_m: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
speed_mps: Mapped[Optional[float]] = mapped_column(Numeric(8, 2))
heading_deg: Mapped[Optional[int]] = mapped_column(SmallInteger)
source: Mapped[int] = mapped_column(Integer, nullable=False)
battery_pct: Mapped[Optional[int]] = mapped_column(Integer)
device_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
server_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)