From 9d7c1ce7b4e6c7137141b522df577fe999d5a692 Mon Sep 17 00:00:00 2001 From: stu2not Date: Thu, 16 Apr 2026 18:19:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=95=B0=E6=8D=AE=E5=BA=93=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E5=B1=82=E4=B8=8E=E5=90=AF=E5=8A=A8=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mini-program/app/dao/child.py | 47 ++++++++++++++++++++------ mini-program/app/db_compat.py | 41 +++++++++++++++++++++++ mini-program/app/main.py | 57 ++++++++++++++------------------ mini-program/app/routers/auth.py | 15 +++++---- 4 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 mini-program/app/db_compat.py diff --git a/mini-program/app/dao/child.py b/mini-program/app/dao/child.py index 2a46201..046bf2c 100644 --- a/mini-program/app/dao/child.py +++ b/mini-program/app/dao/child.py @@ -5,9 +5,21 @@ from typing import Optional from sqlalchemy import text from app.dao import BaseDAO +from app.db_compat import inserted_primary_key class ChildDAO(BaseDAO): + def _create_relation(self, user_id: int, child_id: int) -> None: + self.db.execute( + text( + """ + INSERT INTO parent_child_relations (user_id, child_id, relation_type, is_primary, status) + VALUES (:user_id, :child_id, 9, 0, 1) + """ + ), + {"user_id": user_id, "child_id": child_id}, + ) + def create( self, user_id: int, @@ -18,13 +30,14 @@ class ChildDAO(BaseDAO): result = self.db.execute( text( """ - INSERT INTO children (parent_user_id, child_name, child_gender, child_birthday, status) - VALUES (:user_id, :child_name, :child_gender, :child_birthday, 1) + INSERT INTO children (child_name, child_gender, child_birthday, status) + VALUES (:child_name, :child_gender, :child_birthday, 1) """ ), - {"user_id": user_id, "child_name": child_name, "child_gender": child_gender, "child_birthday": child_birthday}, + {"child_name": child_name, "child_gender": child_gender, "child_birthday": child_birthday}, ) - child_id = int(result.lastrowid) + child_id = inserted_primary_key(result) + self._create_relation(user_id, child_id) self.commit() return child_id @@ -40,18 +53,21 @@ class ChildDAO(BaseDAO): def list_by_parent(self, user_id: int, limit: int = 20, cursor: int = None) -> list[Mapping]: params = {"user_id": user_id, "limit": limit + 1} - where = "parent_user_id = :user_id AND status = 1" - if cursor: - where += " AND child_id < :cursor" + where = "pcr.user_id = :user_id AND pcr.status = 1 AND c.status = 1" + if cursor is not None: + where += " AND c.child_id < :cursor" params["cursor"] = cursor rows = ( self.db.execute( text( f""" - SELECT * FROM children + SELECT c.* + FROM children AS c + JOIN parent_child_relations AS pcr + ON pcr.child_id = c.child_id WHERE {where} - ORDER BY child_id DESC + ORDER BY c.child_id DESC LIMIT :limit """ ), @@ -87,9 +103,18 @@ class ChildDAO(BaseDAO): return ( self.db.execute( text( - "SELECT 1 FROM children WHERE child_id = :child_id AND parent_user_id = :user_id AND status = 1" + """ + SELECT 1 + FROM children AS c + JOIN parent_child_relations AS pcr + ON pcr.child_id = c.child_id + WHERE c.child_id = :child_id + AND pcr.user_id = :user_id + AND c.status = 1 + AND pcr.status = 1 + """ ), {"child_id": child_id, "user_id": user_id}, ).scalar_one_or_none() is not None - ) \ No newline at end of file + ) diff --git a/mini-program/app/db_compat.py b/mini-program/app/db_compat.py new file mode 100644 index 0000000..d3d2260 --- /dev/null +++ b/mini-program/app/db_compat.py @@ -0,0 +1,41 @@ +from collections.abc import Sequence +from typing import Any + +from sqlalchemy.orm import Session + + +def get_db_dialect_name(db: Session) -> str: + bind = db.get_bind() + if bind is None: + raise RuntimeError("Database session is not bound to an engine") + return bind.dialect.name + + +def current_timestamp_sql(db: Session) -> str: + if get_db_dialect_name(db) == "mysql": + return "CURRENT_TIMESTAMP(3)" + return "CURRENT_TIMESTAMP" + + +def select_for_update_clause(db: Session) -> str: + if get_db_dialect_name(db) == "mysql": + return " FOR UPDATE" + return "" + + +def inserted_primary_key(result: Any) -> int: + lastrowid = getattr(result, "lastrowid", None) + if lastrowid is not None: + return int(lastrowid) + + try: + inserted_primary_key = result.inserted_primary_key + except Exception: + inserted_primary_key = None + + if isinstance(inserted_primary_key, Sequence) and inserted_primary_key: + primary_key = inserted_primary_key[0] + if primary_key is not None: + return int(primary_key) + + raise RuntimeError("Could not determine inserted primary key") diff --git a/mini-program/app/main.py b/mini-program/app/main.py index 8279e80..0cc8ab1 100644 --- a/mini-program/app/main.py +++ b/mini-program/app/main.py @@ -1,35 +1,29 @@ import os import logging +import sys +from pathlib import Path from fastapi import FastAPI -try: - # For module mode: `uvicorn app.main:app` - from app.db import check_db_connection, close_db_engine, init_db_tables - from app.logging_setup import configure_logging - from app.middleware.auth import install_auth_middleware - from app.middleware.request_log import install_request_logging_middleware - from app.routers.auth import router as auth_router - from app.routers.wechat_auth import router as wechat_auth_router - from app.routers.health import router as health_router - from app.routers.messages import router as messages_router - from app.routers.parents import router as parents_router - from app.routers.children import router as children_router - from app.routers.bindings import router as bindings_router - from app.settings import settings -except ModuleNotFoundError: - # For script mode: `python app/main.py` or VS Code "Run Python File" - from db import check_db_connection, close_db_engine, init_db_tables - from logging_setup import configure_logging - from middleware.auth import install_auth_middleware - from middleware.request_log import install_request_logging_middleware - from routers.auth import router as auth_router - from routers.health import router as health_router - from routers.messages import router as messages_router - from routers.parents import router as parents_router - from routers.children import router as children_router - from routers.bindings import router as bindings_router - from settings import settings +if __package__ in (None, ""): + # Make `python app/main.py` behave like module execution from the project root. + project_root = Path(__file__).resolve().parent.parent + project_root_str = str(project_root) + if project_root_str not in sys.path: + sys.path.insert(0, project_root_str) + +from app.db import check_db_connection, close_db_engine, init_db_tables +from app.logging_setup import configure_logging +from app.middleware.auth import install_auth_middleware +from app.middleware.request_log import install_request_logging_middleware +from app.routers.auth import router as auth_router +from app.routers.wechat_auth import router as wechat_auth_router +from app.routers.health import router as health_router +from app.routers.messages import router as messages_router +from app.routers.parents import router as parents_router +from app.routers.children import router as children_router +from app.routers.bindings import router as bindings_router +from app.settings import settings configure_logging() @@ -59,12 +53,9 @@ def create_app() -> FastAPI: app.include_router(wechat_auth_router) app.include_router(health_router) app.include_router(messages_router) - try: - app.include_router(parents_router) - app.include_router(children_router) - app.include_router(bindings_router) - except NameError: - pass + app.include_router(parents_router) + app.include_router(children_router) + app.include_router(bindings_router) return app diff --git a/mini-program/app/routers/auth.py b/mini-program/app/routers/auth.py index 248ea13..cdf12bd 100644 --- a/mini-program/app/routers/auth.py +++ b/mini-program/app/routers/auth.py @@ -8,11 +8,13 @@ from sqlalchemy.orm import Session try: # For module mode: `uvicorn app.main:app` from app.db import get_db + from app.db_compat import current_timestamp_sql from app.schemas.auth import LoginRequest, LoginResponse from app.security import create_access_token except ModuleNotFoundError: # For script mode: `python app/main.py` or VS Code "Run Python File" from db import get_db + from db_compat import current_timestamp_sql from schemas.auth import LoginRequest, LoginResponse from security import create_access_token @@ -34,6 +36,7 @@ def login( db: Session = Depends(get_db), ) -> LoginResponse: username_masked = _mask_username(payload.username) + now_sql = current_timestamp_sql(db) with db.begin(): row = ( db.execute( @@ -107,10 +110,10 @@ def login( db.execute( text( - """ + f""" UPDATE chat_user_auth - SET last_login_at = CURRENT_TIMESTAMP(3), - updated_at = CURRENT_TIMESTAMP(3) + SET last_login_at = {now_sql}, + updated_at = {now_sql} WHERE id = :user_auth_id """ ), @@ -118,10 +121,10 @@ def login( ) db.execute( text( - """ + f""" UPDATE chat_user - SET last_login_at = CURRENT_TIMESTAMP(3), - updated_at = CURRENT_TIMESTAMP(3) + SET last_login_at = {now_sql}, + updated_at = {now_sql} WHERE id = :user_id """ ),