From 97d60350b8e8517fd069e8b85a30f379f0efffb3 Mon Sep 17 00:00:00 2001 From: qwas Date: Sat, 31 Jan 2026 21:45:04 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E5=85=A8?= =?UTF-8?q?=E9=83=A8=20sqlite=20=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E8=80=83=E8=99=91=20sqlite=20=E5=85=BC?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 - api/.env.example | 1 - ...add_system_admin_table_and_remove_user_.py | 1 - ...58ec203bd381_add_storage_backends_table.py | 1 - api/app/database.py | 43 +++++++------------ api/pyproject.toml | 1 - api/uv.lock | 16 ------- docker-compose.dev.yml | 1 - 8 files changed, 15 insertions(+), 50 deletions(-) diff --git a/Dockerfile b/Dockerfile index d285eeb..425297e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,6 @@ COPY api/ ./ ENV PATH="/api/.venv/bin:$PATH" -# 拷贝数据目录(确保 sqlite 文件目录存在) 在容器中的数据保存目录为 /api/data # 安装后端依赖 RUN mkdir -p data && pip install uv && uv sync diff --git a/api/.env.example b/api/.env.example index d5ceacf..355a8fc 100644 --- a/api/.env.example +++ b/api/.env.example @@ -5,5 +5,4 @@ SECRET_KEY=change-me-generate-a-secure-random-string ACCESS_TOKEN_EXPIRE_MINUTES=60 REFRESH_TOKEN_EXPIRE_DAYS=7 JWT_ALGORITHM=HS256 -# DATABASE_URL 非必填,如果未配置 DATABASE_URL,默认使用 sqlite # DATABASE_URL=postgres://postgres:password@localhost:5432/postgres diff --git a/api/alembic/versions/54ffc5a9b833_add_system_admin_table_and_remove_user_.py b/api/alembic/versions/54ffc5a9b833_add_system_admin_table_and_remove_user_.py index 3764a9a..007ff06 100644 --- a/api/alembic/versions/54ffc5a9b833_add_system_admin_table_and_remove_user_.py +++ b/api/alembic/versions/54ffc5a9b833_add_system_admin_table_and_remove_user_.py @@ -71,7 +71,6 @@ def upgrade() -> None: ) # 删除 users 表的 role 列 - # PostgreSQL 支持直接删除列,SQLite 需要使用 batch 模式 with op.batch_alter_table("users", schema=None) as batch_op: batch_op.drop_column("role") diff --git a/api/alembic/versions/58ec203bd381_add_storage_backends_table.py b/api/alembic/versions/58ec203bd381_add_storage_backends_table.py index 7581064..c80b51d 100644 --- a/api/alembic/versions/58ec203bd381_add_storage_backends_table.py +++ b/api/alembic/versions/58ec203bd381_add_storage_backends_table.py @@ -61,7 +61,6 @@ def upgrade() -> None: def downgrade() -> None: """删除存储后端配置表""" - # 使用 batch mode 来支持 SQLite with op.batch_alter_table("files", schema=None) as batch_op: batch_op.drop_constraint("fk_files_storage_backend_id", type_="foreignkey") batch_op.drop_column("storage_backend_id") diff --git a/api/app/database.py b/api/app/database.py index 5b0b2b8..329b908 100644 --- a/api/app/database.py +++ b/api/app/database.py @@ -15,9 +15,6 @@ load_dotenv() -# 默认使用异步 sqlite 驱动 aiosqlite -_DEFAULT_SQLITE = "sqlite+aiosqlite:///./data/app.sqlite" - # 支持通过环境变量 `DATABASE_URL` 切换为 Postgres(推荐带 asyncpg 驱动) # 如果用户提供常见的 postgres URI(postgres:// 或 postgresql://), # 会自动把 scheme 转换为 `postgresql+asyncpg://` 以使用 asyncpg @@ -25,11 +22,7 @@ connect_args = {} if raw_db_url: - # 如果是 sqlite,直接使用,不进行后续的 URL 重组(避免 urlunparse 丢失 /// 问题) - if raw_db_url.startswith("sqlite"): - DATABASE_URL = raw_db_url - else: - if raw_db_url.startswith("postgres://"): + if raw_db_url.startswith("postgres://"): raw_db_url = raw_db_url.replace("postgres://", "postgresql+asyncpg://", 1) elif raw_db_url.startswith("postgresql://"): raw_db_url = raw_db_url.replace("postgresql://", "postgresql+asyncpg://", 1) @@ -50,27 +43,21 @@ if "channel_binding" in query_params: query_params.pop("channel_binding") - # 重组 URL - new_query = urlencode(query_params, doseq=True) - parsed = parsed._replace(query=new_query) - DATABASE_URL = urlunparse(parsed) -else: - DATABASE_URL = _DEFAULT_SQLITE - -# 对 sqlite 使用特定 connect_args(aiosqlite 的 check_same_thread) -if DATABASE_URL.startswith("sqlite"): - engine = create_async_engine( - DATABASE_URL, connect_args={"check_same_thread": False} - ) + # 重组 URL + new_query = urlencode(query_params, doseq=True) + parsed = parsed._replace(query=new_query) + DATABASE_URL = urlunparse(parsed) else: - engine = create_async_engine( - DATABASE_URL, - connect_args=connect_args, - pool_size=20, # 增加连接池大小 - max_overflow=40, # 允许超出连接池的额外连接数 - pool_pre_ping=True, # 连接前ping确保连接有效 - pool_recycle=3600, # 1小时后回收连接 - ) + raise ValueError("DATABASE_URL environment variable is required") + +engine = create_async_engine( + DATABASE_URL, + connect_args=connect_args, + pool_size=20, # 增加连接池大小 + max_overflow=40, # 允许超出连接池的额外连接数 + pool_pre_ping=True, # 连接前ping确保连接有效 + pool_recycle=3600, # 1小时后回收连接 +) async_session_maker = async_sessionmaker(engine, expire_on_commit=False) diff --git a/api/pyproject.toml b/api/pyproject.toml index ee56edc..2894dd1 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -5,7 +5,6 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ - "aiosqlite>=0.21.0", "fastapi>=0.123.4", "pydantic>=2.12.5", "python-multipart>=0.0.20", diff --git a/api/uv.lock b/api/uv.lock index 2602111..98a5786 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -6,18 +6,6 @@ resolution-markers = [ "python_full_version < '3.14'", ] -[[package]] -name = "aiosqlite" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, -] - [[package]] name = "alembic" version = "1.17.2" @@ -67,7 +55,6 @@ name = "api" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "aiosqlite" }, { name = "alembic" }, { name = "asyncpg" }, { name = "boto3" }, @@ -84,7 +71,6 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "aiosqlite", specifier = ">=0.21.0" }, { name = "alembic", specifier = ">=1.17.2" }, { name = "asyncpg", specifier = ">=0.31.0" }, { name = "boto3", specifier = ">=1.35.0" }, @@ -360,7 +346,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, @@ -371,7 +356,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 913be11..e034083 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -10,7 +10,6 @@ services: - ./api:/app environment: - SECRET_KEY=dev-secret-key-change-in-production - # - DATABASE_URL=sqlite+aiosqlite:///./data/app.db - DATABASE_URL=postgresql+asyncpg://postgres:postgres@postgres:5432/archivenote command: > bash -c "pip install uv && From 53839bbeba2d6390404c86e688caf45611d9c190 Mon Sep 17 00:00:00 2001 From: qwas Date: Sat, 31 Jan 2026 22:45:09 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/database.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/api/app/database.py b/api/app/database.py index 329b908..be46577 100644 --- a/api/app/database.py +++ b/api/app/database.py @@ -23,25 +23,25 @@ if raw_db_url: if raw_db_url.startswith("postgres://"): - raw_db_url = raw_db_url.replace("postgres://", "postgresql+asyncpg://", 1) - elif raw_db_url.startswith("postgresql://"): - raw_db_url = raw_db_url.replace("postgresql://", "postgresql+asyncpg://", 1) - - # 解析 URL 处理 asyncpg 不支持的参数 - parsed = urlparse(raw_db_url) - query_params = parse_qs(parsed.query) - - # 处理 sslmode - if "sslmode" in query_params: - ssl_mode = query_params.pop("sslmode")[0] - if ssl_mode == "require": - connect_args["ssl"] = "require" - elif ssl_mode == "disable": - connect_args["ssl"] = False - - # 处理 channel_binding (asyncpg 不支持此参数作为 kwarg,移除以避免报错) - if "channel_binding" in query_params: - query_params.pop("channel_binding") + raw_db_url = raw_db_url.replace("postgres://", "postgresql+asyncpg://", 1) + elif raw_db_url.startswith("postgresql://"): + raw_db_url = raw_db_url.replace("postgresql://", "postgresql+asyncpg://", 1) + + # 解析 URL 处理 asyncpg 不支持的参数 + parsed = urlparse(raw_db_url) + query_params = parse_qs(parsed.query) + + # 处理 sslmode + if "sslmode" in query_params: + ssl_mode = query_params.pop("sslmode")[0] + if ssl_mode == "require": + connect_args["ssl"] = "require" + elif ssl_mode == "disable": + connect_args["ssl"] = False + + # 处理 channel_binding (asyncpg 不支持此参数作为 kwarg,移除以避免报错) + if "channel_binding" in query_params: + query_params.pop("channel_binding") # 重组 URL new_query = urlencode(query_params, doseq=True)