From 56dffc300b4b29f1eb09c0d592915cebf5316ac3 Mon Sep 17 00:00:00 2001 From: iammm0 Date: Wed, 7 Jan 2026 11:56:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/database/__init__.py | 17 +++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 469 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 2553 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 4468 bytes api/database/database.py | 51 +++++++++ api/database/models.py | 98 ++++++++++++++++++ 6 files changed, 166 insertions(+) create mode 100644 api/database/__init__.py create mode 100644 api/database/__pycache__/__init__.cpython-312.pyc create mode 100644 api/database/__pycache__/database.cpython-312.pyc create mode 100644 api/database/__pycache__/models.cpython-312.pyc create mode 100644 api/database/database.py create mode 100644 api/database/models.py diff --git a/api/database/__init__.py b/api/database/__init__.py new file mode 100644 index 0000000..3067e8a --- /dev/null +++ b/api/database/__init__.py @@ -0,0 +1,17 @@ +""" +数据库模块 +""" +from .database import get_db, get_async_db, init_db +from .models import User, Conversation, Segment, Chapter, Book + +__all__ = [ + "get_db", + "get_async_db", + "init_db", + "User", + "Conversation", + "Segment", + "Chapter", + "Book", +] + diff --git a/api/database/__pycache__/__init__.cpython-312.pyc b/api/database/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be996f1748bbdc2d1869b1307dcc14576ba20eed GIT binary patch literal 469 zcmZ{gzfQw25XSBNbCR|a@B)k|11vBhQ~}*lCB$L{S#DyQK%7KLijoiamiqlxNJyRHaB`ZX;lcmS3C}|aEsX)7| z&skat)QMi6 zEM!UfuZohONkYgCm`G7D-Ri9S>ba$#M|Bzd-8zV9!MjCv$bzc$llXuwvmnZRn#cak z)+gj=T(XkaQ+q?24ieTb7Nny3q;gT>U55~UXc{!WECAP@!0HQVKZEwC;WV@pRLsdU E-%WLW`Tzg` literal 0 HcmV?d00001 diff --git a/api/database/__pycache__/database.cpython-312.pyc b/api/database/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0763710eb29f63268a8e4b65e74cbbe46c748bab GIT binary patch literal 2553 zcmah~Yitx%6ux&}J3F22zFH_yq1|o3mC*7GNEF>I@P|@~NCIokI-MEXEwj6IW@}r- zq&(DCpuAgAAs8iKS{{Fh1e##iKPLX!N=@rTgNbY@zbN#F(D1`^_eD#h+|ArQ_uO;O zIrlr?ocYCO6A+9$KfM=Du?YQ57P$fI^q52#pQHR`_PovoI##hMHr3hh=%cey+@KXymudxD zXI@*RxDKFTvEqh#mQn=s>~_jm-0~8f62?wl9XotEb>qbNt+Qj@=TpZIr8>`~E*wl9 z>Jjb}x3xZO+GJgo;%Z2ZHiV<9k1|C=HH>g98j*IXx@p;(S@~$wowbyV_Nbh-H$^{L zWsOW%W^cNO`%n-BahQN)sHV8y|BgT4-?|~R?d_V~CB_F@IIga(s;a82(!x6-3%M~? zsnqYb=680xGvZ z9+P8{NH}gZXvz_CnljGi7X7%7paKIUC>PyQ0b;&3`LaD2fP#IMx_M))t7GizuJLa! z`mpW<(^?;+lXt=7BWheyq_|`Xx#DP=$z^K!J(>g?H07^eTer+`@Nc^{0~Fm<524FB2Y zy_-jv(jlhwyM;-n^f$(*&w;%e&@rI!!ZS%g+XR;e__6>X)CW2;K1Now2lY_B7{h-P zf%>_Tvjotv_k}jCsEyu%TJa^i75k``rQmG5`-jw#p48DpW0%h7QNBCy<@l8|4+c8# zoxFJWM)&xwz4tEMymzVV!9Z6nc^$Icu8GN#X0oytGgP1!rPLeb3S~Nh!{i#&G(Kgp zAicow!8DE@14P#1gY!EA6BNz5M++UF^MCQe5x$E*EIjU{GQv}%2(0%Ntij!k7ae!5 z!QZ-8(bwoTcm|3VFrtBLlLsL1f1$V%?T3!{5ugW1(x0+y7S~Hm5A$MV_d|zFpJg*= zak?f=R*3xE$kif$g3g_Q9t*!*H``Cm%!br%H9AxS&r&an6K14G1=8oG>rQU@+?Wvy zPV}U+Ta8#?(ms@{GkAP6&59RgOFS?n+l+^4=eKy#GL+7k*rabnYz*8=VYCCHk?d!% zY6Ya4W!1pG|6x_?^4a^RKS|v<2_#Dsi#46oJ7Fg5;`!cN;-qA&1_%b}kDUw{8gZ8o!yJy($IoB{jS*)H>*PMf!Vd||s zR(ady84*20qGv)t?zz27jx9M+H6oTL#qxig$l--^Lw3*O2@cu3PyVo%JVKP!1xH6* zo*|d#wyR=9tQZn2Mn&fY3p-O2JhFM799@)m^s87tH90k*C_?W>t9FfZ~7!G<$ z0~|ZJ4hJwl_=W}K1V;x7sDzbxO%xOb7P5&2IN%WyWgL+g(nRmW;32V?3%qVgEW?47 zVq&?4$gf&J?<+Aap^y@jLm`uj8KwYVA4!AnPNZEY&B-i$rl^eR5uI>ZCwpU^zA~8kPj7%#h9Yl?Os27kOi>@|nlB}98|JEI~)uHKMO`Wt} zQ_Pe*)9H2cxz{~pA`MzEBNL&4UP30)kJD{xihm74wrJ`)eL3hz|1wU)G(lsGA5s)% zCOL$KKT*XUwDbd4<+2r!tVe4BR9OxY0q<7Wz4a4?8he(#C=UT3_KW-ehZ|V?# jFIsqR&-I$*yYCMdNlD~<#8LQ_M*@f4lP&>!(y9Lo9p|A- literal 0 HcmV?d00001 diff --git a/api/database/__pycache__/models.cpython-312.pyc b/api/database/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72e0cd47ddde57131d54269e2d6688920d9c1c77 GIT binary patch literal 4468 zcmb_gZEV}d875_kqP`_tmhGfTlQiw3E_ObeC0n+&SmHR}PKS5}LU-A{1W7QWQq&M7(TGVgBQC{_gp@FnQqo9CDK9Rfr;Uu1 z@#=z}HF8pp=RzFGEHxe}b%(g)oEp8xsUqoK>hfW!$3tU)#sg^HLlb}|18A>@rT|R` z(1M3%0L=!_zG|-6?exJ@;ty`zdGO|~)!+W&LH*|HJ3n8&_4a2U{51YAoIsIhqN>=$ z)(lb%G4Wi**37b^v+xjb(_UUOiHw=@@Z0K;Zx_&jlwgsDkzQhBLM4M}0x)CdvOuI2Dj$nPLUn;4*x*4By*R9KF-B4f?S02Xgx z;&QANm+-J8NzFVHoSI(`#AP5ZtH#yDaCOrQ5KcYNnWF;U8FpIx!ge zpTDd>xc=V5n{^g(0JFvxd6uY9%}{7nzDTO@f&{0mU2+H$%Z{!q6FM1Lq%L@WV>1+6gPpQFkqc_scc?m@e(C4PgPcIn!$!tDX=Pw zi6wvf7K=>4{H;aq5kzs1Upabs9QV~4uS{1hZT!_L1XANwYR-_7ZH?>NBpD>7X>(ku zXyc#&1z9-`%O$!sRH@P&jN%n3s90}6agAFE_k5h+Ts!+|EHfu847;&n?Z`^1XKrNS zz@43L>QHTDCDS`Mx!|~&!P<$HT;IG<&%3#y+NqWPt@E?>S-1b--Ec29i1l5!_bl#l zbBAlEJ{P#&?F(B!65Rap+S!%N=J}I%Cf&?;VTfe^eBqAZCJ$XbM)x&m)TR~Oal9Vo z`KQwg@KIDFOVA516*bbdBwr(Ol9=iDTZ84Y*y4E##)yzOXl`9!X%EY&u|Qnb!*XqL zs0UPWJP-#~*iiQRcwmH2U|@r!peEE%Ky!T_mI8%kmb0yu^rH#V%`krl*|U24gNL_& z^V#p-h7x2(2LT(SJy1RtJ;e=a>Ig;8qyt!>y)Z#3p>6q$*n&v(Jee{`86*}YhXJ9UV5Ln>EYV(P930lra>^7Az*&rmiejrCAV+i-Ee*d`i_C#(naXx>FtD@8U(fF zH_acc54-t&Ae(__7lb=`cK{St07Zdvg!&t9;+5L4uc-3f(Fr$Gx_ZK&JkEP#A^jU# zp61X2;(fW0@TgiRw13znJi#@OucSxv6iLtEE_Pf2fi#6A1hA%%g3s`UlxTY{3aM9( zfoS3_0T-%?Vv-(&2L|Nv;MThj=RQPvd=DCYd0fc`*Ap=&2L*s zzkkK;Id%1nPgtDW#)SRKonL4p=a&$+EzWm-K@Db6m>oqR?+KCc{ZW<``O>Rq#yw%eI4gPe>GK1(`R9LKebv~c=)p)BdLb~@<~k(K-IT<7E!4( zX?w1}7^0h?fr)0>sbFrr?)*<{-}GBJP{h`3oitn+2yX)!sb|#*ld5R%;NP#)mm!v7 zw!s9*_@;=}!F3k*r-Qi=7n45gD zcFZ^5V{ZCIFyHR}`M&x$-0pp~)4mZOcT>B;h{2SH7Y?|oT~J@=`$s4KbnXx5+}s!> zKwe^GyP4g!6HRx%;wBDX9roFcliirz|ACgT*p7qW15I`ZS?k+zqK(OZuG4Pija-Lh zXYe@hxHd)uOh)7G>0oW%W9>E=%E9A*_1EvL{{APcAKpOX?g8Kl;(95HpRAmQ_8?Vz zpUR3os?xJKhEGk~v=v>hAQ4%@zqt%_wHSCdWJ9vYQO~GbS-Y2-(C_0It<0g5mD{it zi!u1Kegp-wvhZYH{eM{LF>$-wy&ISqtfMp6Rk!d`oAU;k*ks>jH*u)RKAg{JvER?d z&}og2QVl@D)tq zKNB2F2LeoL_@6l8Bk(EWze(?8^-d*E&D-!9?Pu0w@jL!E+RJntS}~osG${Vt#q<2< zp%5SbLg4tV_qpx&xeI^gF5KfTe8L_2g!|g3(Y|}pfm(P?Nb}#keqfD*pS2xXUghWD k27lLHNb{%p>nb*#=GS&)_;2G4{H*OvVV^HJ`0@1nKP-br9RL6T literal 0 HcmV?d00001 diff --git a/api/database/database.py b/api/database/database.py new file mode 100644 index 0000000..0ea70ec --- /dev/null +++ b/api/database/database.py @@ -0,0 +1,51 @@ +""" +数据库连接和初始化 +""" +import os +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker + +from .models import Base + +# 数据库文件路径 +DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./life_echo.db") +ASYNC_DATABASE_URL = DATABASE_URL.replace("sqlite://", "sqlite+aiosqlite://") + +# 创建同步引擎(用于迁移等) +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) + +# 创建异步引擎(用于实际应用) +async_engine = create_async_engine(ASYNC_DATABASE_URL, echo=False) + +# 创建会话工厂 +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +AsyncSessionLocal = async_sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False) + + +def init_db(): + """初始化数据库,创建所有表""" + Base.metadata.create_all(bind=engine) + + +def get_db(): + """获取同步数据库会话(用于迁移等)""" + db = SessionLocal() + try: + yield db + finally: + db.close() + + +async def get_async_db(): + """获取异步数据库会话(用于实际应用)""" + async with AsyncSessionLocal() as session: + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise + finally: + await session.close() + diff --git a/api/database/models.py b/api/database/models.py new file mode 100644 index 0000000..57570e5 --- /dev/null +++ b/api/database/models.py @@ -0,0 +1,98 @@ +""" +数据库模型定义 +""" +from datetime import datetime +from typing import Optional, List +from sqlalchemy import Column, String, Integer, DateTime, Boolean, Text, ForeignKey, JSON +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() + + +class User(Base): + """用户表""" + __tablename__ = "users" + + id = Column(String, primary_key=True) + openid = Column(String, unique=True, nullable=True) # 微信 OpenID + nickname = Column(String, nullable=False) + avatar_url = Column(String, nullable=True) + subscription_type = Column(String, default="free") # free, premium + created_at = Column(DateTime, default=datetime.utcnow) + + # Relationships + conversations = relationship("Conversation", back_populates="user") + chapters = relationship("Chapter", back_populates="user") + books = relationship("Book", back_populates="user") + + +class Conversation(Base): + """对话会话表""" + __tablename__ = "conversations" + + id = Column(String, primary_key=True) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + started_at = Column(DateTime, default=datetime.utcnow) + ended_at = Column(DateTime, nullable=True) + duration_seconds = Column(Integer, default=0) + summary = Column(Text, nullable=True) + status = Column(String, default="active") # active, ended, processing + current_topic = Column(String, nullable=True) + conversation_stage = Column(String, nullable=True) # childhood, education, career, family, beliefs, summary + + # Relationships + user = relationship("User", back_populates="conversations") + segments = relationship("Segment", back_populates="conversation", cascade="all, delete-orphan") + + +class Segment(Base): + """对话段落表""" + __tablename__ = "segments" + + id = Column(String, primary_key=True) + conversation_id = Column(String, ForeignKey("conversations.id"), nullable=False) + audio_url = Column(String, nullable=True) + transcript_text = Column(Text, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + processed = Column(Boolean, default=False) + topic_category = Column(String, nullable=True) + agent_response = Column(Text, nullable=True) + + # Relationships + conversation = relationship("Conversation", back_populates="segments") + + +class Chapter(Base): + """章节表""" + __tablename__ = "chapters" + + id = Column(String, primary_key=True) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + title = Column(String, nullable=False) + content = Column(Text, nullable=False) + order_index = Column(Integer, nullable=False) + status = Column(String, default="draft") # draft, completed + images = Column(JSON, nullable=True) # 图片 URL 列表 + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + category = Column(String, nullable=True) # 章节分类 + + # Relationships + user = relationship("User", back_populates="chapters") + + +class Book(Base): + """回忆录表""" + __tablename__ = "books" + + id = Column(String, primary_key=True) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + title = Column(String, nullable=False) + total_pages = Column(Integer, default=0) + total_words = Column(Integer, default=0) + cover_image_url = Column(String, nullable=True) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # Relationships + user = relationship("User", back_populates="books") +