Skip to content

Commit f390d14

Browse files
authored
feat(deploy): 支持数据库自动迁移 (#125)
* feat(Docker): 优化 Dockerfile 和入口脚本,添加多阶段构建和数据库迁移步骤 * feat(Docker): 重构 Dockerfile,移除多阶段构建,优化虚拟环境创建和依赖安装步骤 feat(entrypoint): 更新入口脚本,确保使用预装虚拟环境并修正启动命令 * 删除中文注释,避免报错 * 更新迁移文件
1 parent ee91b34 commit f390d14

File tree

5 files changed

+164
-322
lines changed

5 files changed

+164
-322
lines changed

backend/Dockerfile

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 使用官方 Python 3.11 slim 镜像作为基础镜像
1+
# 使用官方 Python 3.11 slim 镜像
22
FROM python:3.11-slim
33

44
# 设置工作目录
@@ -9,12 +9,22 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
99
PYTHONUNBUFFERED=1 \
1010
PYTHONPATH=/app
1111

12-
# 复制依赖文件(利用 Docker 层缓存)
12+
# 安装系统依赖
13+
RUN apt-get update && apt-get install -y --no-install-recommends \
14+
build-essential \
15+
curl \
16+
&& rm -rf /var/lib/apt/lists/*
17+
18+
# 复制依赖文件
1319
COPY pyproject.toml ./
1420

15-
# 安装 uv 和项目依赖到系统 Python(避免虚拟环境开销)
16-
RUN pip install --no-cache-dir uv && \
17-
uv pip install --no-cache-dir --system -r pyproject.toml
21+
# 安装 uv
22+
RUN pip install --no-cache-dir uv
23+
24+
# 创建虚拟环境并安装所有依赖(内置到镜像)
25+
RUN uv venv && \
26+
. .venv/bin/activate && \
27+
uv pip install -r pyproject.toml
1828

1929
# 复制应用代码
2030
COPY . .
@@ -25,6 +35,5 @@ RUN chmod +x docker-entrypoint.sh
2535
# 暴露端口
2636
EXPOSE 8000
2737

28-
2938
# 启动命令
3039
ENTRYPOINT ["./docker-entrypoint.sh"]

backend/alembic.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ path_separator = os
8484
# database URL. This is consumed by the user-maintained env.py script only.
8585
# other means of configuring database URLs may be customized within the env.py
8686
# file.
87-
# 使用环境变量中的数据库URL
8887
sqlalchemy.url = %ENV DATABASE_URL
8988

9089

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""initial_migration
2+
3+
Revision ID: 2952aa9a98f3
4+
Revises:
5+
Create Date: 2025-10-04 17:23:05.644899
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '2952aa9a98f3'
16+
down_revision: Union[str, Sequence[str], None] = None
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_table('system_config',
25+
sa.Column('id', sa.Integer(), nullable=False),
26+
sa.Column('is_allow_register', sa.Boolean(), nullable=False),
27+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
28+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
29+
sa.PrimaryKeyConstraint('id')
30+
)
31+
op.create_index(op.f('ix_system_config_id'), 'system_config', ['id'], unique=False)
32+
op.create_table('users',
33+
sa.Column('id', sa.Integer(), nullable=False),
34+
sa.Column('username', sa.String(length=50), nullable=False),
35+
sa.Column('email', sa.String(length=100), nullable=False),
36+
sa.Column('password_hash', sa.String(length=255), nullable=False),
37+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
38+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
39+
sa.Column('is_active', sa.Boolean(), nullable=True),
40+
sa.PrimaryKeyConstraint('id')
41+
)
42+
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
43+
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
44+
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
45+
op.create_table('work_flow_states',
46+
sa.Column('id', sa.Integer(), nullable=False),
47+
sa.Column('work_id', sa.String(length=50), nullable=False),
48+
sa.Column('current_state', sa.String(length=50), nullable=False),
49+
sa.Column('previous_state', sa.String(length=50), nullable=True),
50+
sa.Column('state_data', sa.JSON(), nullable=True),
51+
sa.Column('transition_reason', sa.Text(), nullable=True),
52+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
53+
sa.PrimaryKeyConstraint('id')
54+
)
55+
op.create_index(op.f('ix_work_flow_states_id'), 'work_flow_states', ['id'], unique=False)
56+
op.create_index(op.f('ix_work_flow_states_work_id'), 'work_flow_states', ['work_id'], unique=False)
57+
op.create_table('chat_sessions',
58+
sa.Column('id', sa.Integer(), nullable=False),
59+
sa.Column('session_id', sa.String(length=100), nullable=False),
60+
sa.Column('work_id', sa.String(length=50), nullable=False),
61+
sa.Column('system_type', sa.String(length=20), nullable=False),
62+
sa.Column('title', sa.String(length=200), nullable=True),
63+
sa.Column('status', sa.String(length=20), nullable=True),
64+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
65+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
66+
sa.Column('created_by', sa.Integer(), nullable=False),
67+
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
68+
sa.PrimaryKeyConstraint('id')
69+
)
70+
op.create_index(op.f('ix_chat_sessions_id'), 'chat_sessions', ['id'], unique=False)
71+
op.create_index(op.f('ix_chat_sessions_session_id'), 'chat_sessions', ['session_id'], unique=True)
72+
op.create_index(op.f('ix_chat_sessions_work_id'), 'chat_sessions', ['work_id'], unique=False)
73+
op.create_table('model_configs',
74+
sa.Column('id', sa.Integer(), nullable=False),
75+
sa.Column('type', sa.String(length=50), nullable=False),
76+
sa.Column('model_id', sa.String(length=50), nullable=False),
77+
sa.Column('base_url', sa.String(length=100), nullable=False),
78+
sa.Column('api_key', sa.String(length=255), nullable=False),
79+
sa.Column('is_active', sa.Boolean(), nullable=True),
80+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
81+
sa.Column('created_by', sa.Integer(), nullable=False),
82+
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
83+
sa.PrimaryKeyConstraint('id')
84+
)
85+
op.create_index(op.f('ix_model_configs_id'), 'model_configs', ['id'], unique=False)
86+
op.create_table('paper_templates',
87+
sa.Column('id', sa.Integer(), nullable=False),
88+
sa.Column('name', sa.String(length=100), nullable=False),
89+
sa.Column('description', sa.Text(), nullable=True),
90+
sa.Column('category', sa.String(length=50), nullable=True),
91+
sa.Column('file_path', sa.String(length=500), nullable=False),
92+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
93+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
94+
sa.Column('is_public', sa.Boolean(), nullable=True),
95+
sa.Column('created_by', sa.Integer(), nullable=False),
96+
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
97+
sa.PrimaryKeyConstraint('id')
98+
)
99+
op.create_index(op.f('ix_paper_templates_id'), 'paper_templates', ['id'], unique=False)
100+
op.create_table('works',
101+
sa.Column('id', sa.Integer(), nullable=False),
102+
sa.Column('work_id', sa.String(length=50), nullable=False),
103+
sa.Column('title', sa.String(length=200), nullable=False),
104+
sa.Column('description', sa.Text(), nullable=True),
105+
sa.Column('status', sa.String(length=50), nullable=False),
106+
sa.Column('progress', sa.Integer(), nullable=True),
107+
sa.Column('tags', sa.Text(), nullable=True),
108+
sa.Column('template_id', sa.Integer(), nullable=True),
109+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
110+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
111+
sa.Column('created_by', sa.Integer(), nullable=False),
112+
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
113+
sa.ForeignKeyConstraint(['template_id'], ['paper_templates.id'], ),
114+
sa.PrimaryKeyConstraint('id')
115+
)
116+
op.create_index(op.f('ix_works_id'), 'works', ['id'], unique=False)
117+
op.create_index(op.f('ix_works_work_id'), 'works', ['work_id'], unique=True)
118+
# ### end Alembic commands ###
119+
120+
121+
def downgrade() -> None:
122+
"""Downgrade schema."""
123+
# ### commands auto generated by Alembic - please adjust! ###
124+
op.drop_index(op.f('ix_works_work_id'), table_name='works')
125+
op.drop_index(op.f('ix_works_id'), table_name='works')
126+
op.drop_table('works')
127+
op.drop_index(op.f('ix_paper_templates_id'), table_name='paper_templates')
128+
op.drop_table('paper_templates')
129+
op.drop_index(op.f('ix_model_configs_id'), table_name='model_configs')
130+
op.drop_table('model_configs')
131+
op.drop_index(op.f('ix_chat_sessions_work_id'), table_name='chat_sessions')
132+
op.drop_index(op.f('ix_chat_sessions_session_id'), table_name='chat_sessions')
133+
op.drop_index(op.f('ix_chat_sessions_id'), table_name='chat_sessions')
134+
op.drop_table('chat_sessions')
135+
op.drop_index(op.f('ix_work_flow_states_work_id'), table_name='work_flow_states')
136+
op.drop_index(op.f('ix_work_flow_states_id'), table_name='work_flow_states')
137+
op.drop_table('work_flow_states')
138+
op.drop_index(op.f('ix_users_username'), table_name='users')
139+
op.drop_index(op.f('ix_users_id'), table_name='users')
140+
op.drop_index(op.f('ix_users_email'), table_name='users')
141+
op.drop_table('users')
142+
op.drop_index(op.f('ix_system_config_id'), table_name='system_config')
143+
op.drop_table('system_config')
144+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)