diff --git a/app/__pycache__/config.cpython-313.pyc b/app/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000..257ba84 Binary files /dev/null and b/app/__pycache__/config.cpython-313.pyc differ diff --git a/app/__pycache__/db.cpython-313.pyc b/app/__pycache__/db.cpython-313.pyc new file mode 100644 index 0000000..9aaf5a1 Binary files /dev/null and b/app/__pycache__/db.cpython-313.pyc differ diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..5c98650 Binary files /dev/null and b/app/__pycache__/main.cpython-313.pyc differ diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..bcc64cd --- /dev/null +++ b/app/config.py @@ -0,0 +1,48 @@ +# app/config.py +import os +from functools import lru_cache + + +class Settings: + """ + 后端配置: + - APP_ENV: dev / prod + - DB_*: 数据库连接参数 + - 默认库:dev 用 LLZQ-test;prod 用 LLZQ + """ + + def __init__(self) -> None: + self.app_env = os.getenv("APP_ENV", "dev").lower() + + self.db_host = os.getenv("DB_HOST", "127.0.0.1") + self.db_port = int(os.getenv("DB_PORT", "5432")) + self.db_user = os.getenv("DB_USER", "local-Python") + self.db_password = os.getenv("DB_PASSWORD", "") + + # 默认连接的库,根据 APP_ENV 决定 + if self.app_env == "prod": + self.db_name_default = "LLZQ" + else: + self.db_name_default = "LLZQ-test" + + self.db_schema = os.getenv("DB_SCHEMA", "XCX") + + def resolve_db_name(self, env_header: str | None) -> str: + """ + 根据前端传来的 X-LLZQ-Env 决定最终使用哪个 DB: + - "prod" => LLZQ + - 其它/为空 => LLZQ-test + """ + if env_header is None: + return self.db_name_default + + env_header = env_header.lower() + if env_header == "prod": + return "LLZQ" + else: + return "LLZQ-test" + + +@lru_cache() +def get_settings() -> Settings: + return Settings() \ No newline at end of file diff --git a/app/db.py b/app/db.py new file mode 100644 index 0000000..614d9ae --- /dev/null +++ b/app/db.py @@ -0,0 +1,42 @@ +# app/db.py +from contextlib import contextmanager +from typing import Iterator + +import psycopg2 +from psycopg2.extras import RealDictCursor + +from .config import get_settings + + +@contextmanager +def get_connection(db_name: str) -> Iterator[psycopg2.extensions.connection]: + """ + 简单连接管理:每次请求开一个连接,用完就关。 + 当前访问量不高,这种写法足够。 + """ + settings = get_settings() + conn = psycopg2.connect( + host=settings.db_host, + port=settings.db_port, + user=settings.db_user, + password=settings.db_password, + dbname=db_name, + ) + try: + # 设置 schema = XCX + with conn.cursor() as cur: + cur.execute("SET search_path TO %s;", (settings.db_schema,)) + yield conn + finally: + conn.close() + + +def check_health(db_name: str) -> bool: + """ + DB 健康检查:SELECT 1 + """ + with get_connection(db_name) as conn: + with conn.cursor(cursor_factory=RealDictCursor) as cur: + cur.execute("SELECT 1 AS v;") + row = cur.fetchone() + return row["v"] == 1 \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..62cf7c4 --- /dev/null +++ b/app/main.py @@ -0,0 +1,51 @@ +# app/main.py +from fastapi import FastAPI, Header, Depends + +from .config import get_settings +from .db import check_health + +app = FastAPI( + title="LLZQ Backend", + version="0.1.0", +) + + +def resolve_db_name(x_llzq_env: str | None = Header(default=None, alias="X-LLZQ-Env")) -> str: + """ + 从请求头 X-LLZQ-Env 中解析要用的 DB 名。 + """ + settings = get_settings() + return settings.resolve_db_name(x_llzq_env) + + +@app.get("/api/ping") +def ping(db_name: str = Depends(resolve_db_name)): + """ + 最简单调通接口: + - 返回当前后端环境 + - 返回实际连接的 DB 名 + - 简单做一个 DB 健康检查 + """ + settings = get_settings() + ok = False + error_msg = None + + try: + ok = check_health(db_name) + except Exception as e: + error_msg = str(e) + + return { + "ok": ok, + "app_env": settings.app_env, + "db": db_name, + "error": error_msg, + } + + +@app.get("/api/healthz") +def healthz(): + """ + 给 Nginx / 监控用的探活接口,不查 DB。 + """ + return {"status": "ok"} \ No newline at end of file diff --git a/logs/llzq_api_8000.0.err.log b/logs/llzq_api_8000.0.err.log new file mode 100644 index 0000000..395d577 --- /dev/null +++ b/logs/llzq_api_8000.0.err.log @@ -0,0 +1 @@ +INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) diff --git a/logs/llzq_api_8000.0.out.log b/logs/llzq_api_8000.0.out.log new file mode 100644 index 0000000..25b182d --- /dev/null +++ b/logs/llzq_api_8000.0.out.log @@ -0,0 +1 @@ +INFO: 100.64.0.1:59692 - "GET /api/ping HTTP/1.1" 200 OK diff --git a/logs/llzq_api_8000.1.err.log b/logs/llzq_api_8000.1.err.log new file mode 100644 index 0000000..bca2c3e --- /dev/null +++ b/logs/llzq_api_8000.1.err.log @@ -0,0 +1 @@ +INFO: Application startup complete. diff --git a/logs/llzq_api_8000.1.out.log b/logs/llzq_api_8000.1.out.log new file mode 100644 index 0000000..deeb384 --- /dev/null +++ b/logs/llzq_api_8000.1.out.log @@ -0,0 +1 @@ +INFO: 100.64.0.1:59690 - "GET /api/healthz HTTP/1.1" 200 OK diff --git a/logs/llzq_api_8000.2.err.log b/logs/llzq_api_8000.2.err.log new file mode 100644 index 0000000..7f800df --- /dev/null +++ b/logs/llzq_api_8000.2.err.log @@ -0,0 +1 @@ +INFO: Waiting for application startup. diff --git a/logs/llzq_api_8000.2.out.log b/logs/llzq_api_8000.2.out.log new file mode 100644 index 0000000..420732f --- /dev/null +++ b/logs/llzq_api_8000.2.out.log @@ -0,0 +1 @@ +INFO: 100.64.0.1:60268 - "GET /api/ping HTTP/1.1" 200 OK diff --git a/logs/llzq_api_8000.3.err.log b/logs/llzq_api_8000.3.err.log new file mode 100644 index 0000000..8396257 --- /dev/null +++ b/logs/llzq_api_8000.3.err.log @@ -0,0 +1 @@ +INFO: Started server process [11432] diff --git a/logs/llzq_api_8000.3.out.log b/logs/llzq_api_8000.3.out.log new file mode 100644 index 0000000..1ccd2bf --- /dev/null +++ b/logs/llzq_api_8000.3.out.log @@ -0,0 +1 @@ +INFO: 100.64.0.1:60266 - "GET /api/healthz HTTP/1.1" 200 OK diff --git a/logs/llzq_api_8000.4.out.log b/logs/llzq_api_8000.4.out.log new file mode 100644 index 0000000..e3d21f8 --- /dev/null +++ b/logs/llzq_api_8000.4.out.log @@ -0,0 +1 @@ +INFO: 127.0.0.1:56463 - "GET /api/ping HTTP/1.1" 200 OK diff --git a/logs/llzq_api_8000.5.out.log b/logs/llzq_api_8000.5.out.log new file mode 100644 index 0000000..6e25fb7 --- /dev/null +++ b/logs/llzq_api_8000.5.out.log @@ -0,0 +1 @@ +INFO: 127.0.0.1:56463 - "GET /api/healthz HTTP/1.1" 200 OK diff --git a/logs/llzq_api_8000.err.log b/logs/llzq_api_8000.err.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/llzq_api_8000.out.log b/logs/llzq_api_8000.out.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/llzq_api_8000.wrapper.log b/logs/llzq_api_8000.wrapper.log new file mode 100644 index 0000000..2ee775f --- /dev/null +++ b/logs/llzq_api_8000.wrapper.log @@ -0,0 +1,11 @@ +2025-11-19 04:51:16,714 DEBUG - Starting WinSW in console mode +2025-11-19 04:51:16,728 INFO - Installing service 'LLZQ API (FastAPI 8000) (llzq-api-8000)'... +2025-11-19 04:51:16,755 INFO - Service 'LLZQ API (FastAPI 8000) (llzq-api-8000)' was installed successfully. +2025-11-19 04:51:16,909 DEBUG - Starting WinSW in console mode +2025-11-19 04:51:16,925 INFO - Starting service 'LLZQ API (FastAPI 8000) (llzq-api-8000)'... +2025-11-19 04:51:17,008 DEBUG - Starting WinSW in service mode +2025-11-19 04:51:17,017 INFO - Service 'LLZQ API (FastAPI 8000) (llzq-api-8000)' started successfully. +2025-11-19 04:51:17,024 INFO - Starting D:\LLZQ\LLZQ-server\.venv\Scripts\uvicorn.exe app.main:app --host 0.0.0.0 --port 8000 +2025-11-19 04:51:17,041 INFO - Started process 10844 +2025-11-19 04:51:17,047 DEBUG - Forwarding logs of the process System.Diagnostics.Process (uvicorn) to WinSW.SizeBasedRollingLogAppender +2025-11-19 04:51:18,533 DEBUG - Starting WinSW in console mode