小程序后端补充数据库兼容层与启动适配
This commit is contained in:
@@ -5,9 +5,21 @@ from typing import Optional
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
||||||
from app.dao import BaseDAO
|
from app.dao import BaseDAO
|
||||||
|
from app.db_compat import inserted_primary_key
|
||||||
|
|
||||||
|
|
||||||
class ChildDAO(BaseDAO):
|
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(
|
def create(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
@@ -18,13 +30,14 @@ class ChildDAO(BaseDAO):
|
|||||||
result = self.db.execute(
|
result = self.db.execute(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
INSERT INTO children (parent_user_id, child_name, child_gender, child_birthday, status)
|
INSERT INTO children (child_name, child_gender, child_birthday, status)
|
||||||
VALUES (:user_id, :child_name, :child_gender, :child_birthday, 1)
|
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()
|
self.commit()
|
||||||
return child_id
|
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]:
|
def list_by_parent(self, user_id: int, limit: int = 20, cursor: int = None) -> list[Mapping]:
|
||||||
params = {"user_id": user_id, "limit": limit + 1}
|
params = {"user_id": user_id, "limit": limit + 1}
|
||||||
where = "parent_user_id = :user_id AND status = 1"
|
where = "pcr.user_id = :user_id AND pcr.status = 1 AND c.status = 1"
|
||||||
if cursor:
|
if cursor is not None:
|
||||||
where += " AND child_id < :cursor"
|
where += " AND c.child_id < :cursor"
|
||||||
params["cursor"] = cursor
|
params["cursor"] = cursor
|
||||||
|
|
||||||
rows = (
|
rows = (
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
text(
|
text(
|
||||||
f"""
|
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}
|
WHERE {where}
|
||||||
ORDER BY child_id DESC
|
ORDER BY c.child_id DESC
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
@@ -87,9 +103,18 @@ class ChildDAO(BaseDAO):
|
|||||||
return (
|
return (
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
text(
|
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},
|
{"child_id": child_id, "user_id": user_id},
|
||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|||||||
41
mini-program/app/db_compat.py
Normal file
41
mini-program/app/db_compat.py
Normal file
@@ -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")
|
||||||
@@ -1,35 +1,29 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
try:
|
if __package__ in (None, ""):
|
||||||
# For module mode: `uvicorn app.main:app`
|
# Make `python app/main.py` behave like module execution from the project root.
|
||||||
from app.db import check_db_connection, close_db_engine, init_db_tables
|
project_root = Path(__file__).resolve().parent.parent
|
||||||
from app.logging_setup import configure_logging
|
project_root_str = str(project_root)
|
||||||
from app.middleware.auth import install_auth_middleware
|
if project_root_str not in sys.path:
|
||||||
from app.middleware.request_log import install_request_logging_middleware
|
sys.path.insert(0, project_root_str)
|
||||||
from app.routers.auth import router as auth_router
|
|
||||||
from app.routers.wechat_auth import router as wechat_auth_router
|
from app.db import check_db_connection, close_db_engine, init_db_tables
|
||||||
from app.routers.health import router as health_router
|
from app.logging_setup import configure_logging
|
||||||
from app.routers.messages import router as messages_router
|
from app.middleware.auth import install_auth_middleware
|
||||||
from app.routers.parents import router as parents_router
|
from app.middleware.request_log import install_request_logging_middleware
|
||||||
from app.routers.children import router as children_router
|
from app.routers.auth import router as auth_router
|
||||||
from app.routers.bindings import router as bindings_router
|
from app.routers.wechat_auth import router as wechat_auth_router
|
||||||
from app.settings import settings
|
from app.routers.health import router as health_router
|
||||||
except ModuleNotFoundError:
|
from app.routers.messages import router as messages_router
|
||||||
# For script mode: `python app/main.py` or VS Code "Run Python File"
|
from app.routers.parents import router as parents_router
|
||||||
from db import check_db_connection, close_db_engine, init_db_tables
|
from app.routers.children import router as children_router
|
||||||
from logging_setup import configure_logging
|
from app.routers.bindings import router as bindings_router
|
||||||
from middleware.auth import install_auth_middleware
|
from app.settings import settings
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
configure_logging()
|
configure_logging()
|
||||||
@@ -59,12 +53,9 @@ def create_app() -> FastAPI:
|
|||||||
app.include_router(wechat_auth_router)
|
app.include_router(wechat_auth_router)
|
||||||
app.include_router(health_router)
|
app.include_router(health_router)
|
||||||
app.include_router(messages_router)
|
app.include_router(messages_router)
|
||||||
try:
|
app.include_router(parents_router)
|
||||||
app.include_router(parents_router)
|
app.include_router(children_router)
|
||||||
app.include_router(children_router)
|
app.include_router(bindings_router)
|
||||||
app.include_router(bindings_router)
|
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ from sqlalchemy.orm import Session
|
|||||||
try:
|
try:
|
||||||
# For module mode: `uvicorn app.main:app`
|
# For module mode: `uvicorn app.main:app`
|
||||||
from app.db import get_db
|
from app.db import get_db
|
||||||
|
from app.db_compat import current_timestamp_sql
|
||||||
from app.schemas.auth import LoginRequest, LoginResponse
|
from app.schemas.auth import LoginRequest, LoginResponse
|
||||||
from app.security import create_access_token
|
from app.security import create_access_token
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
# For script mode: `python app/main.py` or VS Code "Run Python File"
|
# For script mode: `python app/main.py` or VS Code "Run Python File"
|
||||||
from db import get_db
|
from db import get_db
|
||||||
|
from db_compat import current_timestamp_sql
|
||||||
from schemas.auth import LoginRequest, LoginResponse
|
from schemas.auth import LoginRequest, LoginResponse
|
||||||
from security import create_access_token
|
from security import create_access_token
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ def login(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> LoginResponse:
|
) -> LoginResponse:
|
||||||
username_masked = _mask_username(payload.username)
|
username_masked = _mask_username(payload.username)
|
||||||
|
now_sql = current_timestamp_sql(db)
|
||||||
with db.begin():
|
with db.begin():
|
||||||
row = (
|
row = (
|
||||||
db.execute(
|
db.execute(
|
||||||
@@ -107,10 +110,10 @@ def login(
|
|||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
text(
|
text(
|
||||||
"""
|
f"""
|
||||||
UPDATE chat_user_auth
|
UPDATE chat_user_auth
|
||||||
SET last_login_at = CURRENT_TIMESTAMP(3),
|
SET last_login_at = {now_sql},
|
||||||
updated_at = CURRENT_TIMESTAMP(3)
|
updated_at = {now_sql}
|
||||||
WHERE id = :user_auth_id
|
WHERE id = :user_auth_id
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
@@ -118,10 +121,10 @@ def login(
|
|||||||
)
|
)
|
||||||
db.execute(
|
db.execute(
|
||||||
text(
|
text(
|
||||||
"""
|
f"""
|
||||||
UPDATE chat_user
|
UPDATE chat_user
|
||||||
SET last_login_at = CURRENT_TIMESTAMP(3),
|
SET last_login_at = {now_sql},
|
||||||
updated_at = CURRENT_TIMESTAMP(3)
|
updated_at = {now_sql}
|
||||||
WHERE id = :user_id
|
WHERE id = :user_id
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user