From d6fefd613825977685641ff46beb13e08d56fa34 Mon Sep 17 00:00:00 2001 From: "949036910@qq.com" <> Date: Sat, 30 May 2026 12:46:17 +0800 Subject: [PATCH] 11 --- alembic/env.py | 2 +- alembic/versions/016_create_configs_table.py | 41 ++++++++++++++++++++ app/main.py | 33 ++++++++++++++++ app/models/__init__.py | 3 +- app/models/config.py | 18 +++++++++ app/schemas/release_version.py | 9 +++++ app/services/config_store.py | 34 ++++++++++++++++ 7 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 alembic/versions/016_create_configs_table.py create mode 100644 app/models/config.py create mode 100644 app/schemas/release_version.py create mode 100644 app/services/config_store.py diff --git a/alembic/env.py b/alembic/env.py index dc94325..e8b5d09 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import async_engine_from_config from app.config import get_settings from app.database import Base -from app.models import CardKey, DesktopConfig, Oem, User # noqa: F401 — 注册元数据 +from app.models import CardKey, Config, DesktopConfig, Oem, User # noqa: F401 — 注册元数据 config = context.config settings = get_settings() diff --git a/alembic/versions/016_create_configs_table.py b/alembic/versions/016_create_configs_table.py new file mode 100644 index 0000000..ec667f8 --- /dev/null +++ b/alembic/versions/016_create_configs_table.py @@ -0,0 +1,41 @@ +"""create configs table""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "016" +down_revision: Union[str, None] = "015" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + "configs", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("name", sa.String(length=64), nullable=False), + sa.Column("value", sa.Text(), nullable=False), + sa.Column("mark", sa.String(length=255), nullable=True), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.DateTime(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name", name="uq_configs_name"), + ) + op.create_index(op.f("ix_configs_name"), "configs", ["name"], unique=False) + + +def downgrade() -> None: + op.drop_index(op.f("ix_configs_name"), table_name="configs") + op.drop_table("configs") diff --git a/app/main.py b/app/main.py index b8fac1f..b01fdfa 100644 --- a/app/main.py +++ b/app/main.py @@ -7,9 +7,13 @@ from fastapi.staticfiles import StaticFiles from app.api.v1.router import api_router from app.config import get_settings from app.core.branding_images import IMAGES_DIR, ensure_images_dir +from app.dependencies import DbSession from app.middleware.api_crypto import ApiCryptoMiddleware from app.redis_client import close_redis, init_redis from app.schemas.common import ApiResponse +from app.schemas.release_version import ReleaseVersionOut, SetReleaseVersionIn +from app.services import config_store +from app.services.config_store import RELEASE_VERSION_KEY settings = get_settings() @@ -41,6 +45,35 @@ def create_app() -> FastAPI: async def health() -> ApiResponse[dict]: return ApiResponse(ok=True, message="服务正常", data={"status": "up"}) + @app.get("/api/checkupdate", response_model=ApiResponse[ReleaseVersionOut]) + async def check_update(db: DbSession) -> ApiResponse[ReleaseVersionOut]: + version = await config_store.get_config_value(db, RELEASE_VERSION_KEY) + if not version: + return ApiResponse(ok=False, message="未配置 releaseversion") + return ApiResponse( + ok=True, + message="", + data=ReleaseVersionOut(version=version), + ) + + @app.post("/api/setreleaseversion", response_model=ApiResponse[ReleaseVersionOut]) + async def set_release_version( + body: SetReleaseVersionIn, + db: DbSession, + ) -> ApiResponse[ReleaseVersionOut]: + version = body.version.strip() + saved = await config_store.set_config_value( + db, + RELEASE_VERSION_KEY, + version, + mark="桌面端发布版本", + ) + return ApiResponse( + ok=True, + message="已更新 releaseversion", + data=ReleaseVersionOut(version=saved), + ) + ensure_images_dir() app.mount( "/images", diff --git a/app/models/__init__.py b/app/models/__init__.py index 627c98b..d5a0f53 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1,6 +1,7 @@ from app.models.card_key import CardKey +from app.models.config import Config from app.models.desktopConfig import DesktopConfig from app.models.oem import Oem from app.models.user import User -__all__ = ["User", "DesktopConfig", "CardKey", "Oem"] +__all__ = ["User", "DesktopConfig", "CardKey", "Oem", "Config"] diff --git a/app/models/config.py b/app/models/config.py new file mode 100644 index 0000000..e8c9b1e --- /dev/null +++ b/app/models/config.py @@ -0,0 +1,18 @@ +from datetime import datetime + +from sqlalchemy import DateTime, String, Text, UniqueConstraint, func +from sqlalchemy.orm import Mapped, mapped_column + +from app.database import Base + + +class Config(Base): + __tablename__ = "configs" + __table_args__ = (UniqueConstraint("name", name="uq_configs_name"),) + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(64), index=True) + value: Mapped[str] = mapped_column(Text) + mark: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) diff --git a/app/schemas/release_version.py b/app/schemas/release_version.py new file mode 100644 index 0000000..020cbc7 --- /dev/null +++ b/app/schemas/release_version.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel, Field + + +class ReleaseVersionOut(BaseModel): + version: str + + +class SetReleaseVersionIn(BaseModel): + version: str = Field(min_length=1, max_length=64) diff --git a/app/services/config_store.py b/app/services/config_store.py new file mode 100644 index 0000000..ee6f9d0 --- /dev/null +++ b/app/services/config_store.py @@ -0,0 +1,34 @@ +"""configs 表:全局键值配置(如 releaseversion)。""" + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.config import Config + +RELEASE_VERSION_KEY = "releaseversion" + + +async def get_config_value(db: AsyncSession, name: str) -> str | None: + result = await db.execute(select(Config).where(Config.name == name)) + row = result.scalar_one_or_none() + return row.value if row else None + + +async def set_config_value( + db: AsyncSession, + name: str, + value: str, + *, + mark: str | None = None, +) -> str: + result = await db.execute(select(Config).where(Config.name == name)) + row = result.scalar_one_or_none() + if row is None: + row = Config(name=name, value=value, mark=mark) + db.add(row) + else: + row.value = value + if mark is not None: + row.mark = mark + await db.flush() + return row.value