This commit is contained in:
吕新雨
2026-01-28 20:50:17 +08:00
commit 049995692d
27 changed files with 1661 additions and 0 deletions

6
server/app/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
"""
后端应用包入口。
说明:目前仅提供项目初始化骨架,后续会逐步补齐业务模块。
"""

71
server/app/core/config.py Normal file
View File

@@ -0,0 +1,71 @@
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from typing import Literal, Optional
from pydantic_settings import BaseSettings, SettingsConfigDict
def _guess_env_file(app_env: str) -> Optional[str]:
"""
根据 APP_ENV 选择 env 文件。
说明:
- `.env.dev` / `.env.prod` 不进入仓库,通常由运维在部署时放到 `server/` 目录
- 如果文件不存在,则返回 None让系统从“真实环境变量”读取
"""
base_dir = Path(__file__).resolve().parents[2] # .../server/app -> .../server
candidates = {
"dev": base_dir / ".env.dev",
"prod": base_dir / ".env.prod",
}
p = candidates.get(app_env)
if p and p.exists():
return str(p)
return None
class Settings(BaseSettings):
"""
应用配置(统一从环境变量读取)。
注意:请不要把真实账号密码写入仓库;使用 `.env.dev/.env.prod` 或部署系统注入。
"""
app_env: Literal["dev", "prod"] = "dev"
app_name: str = "mindfulness-server"
app_host: str = "0.0.0.0"
app_port: int = 8000
database_url: str
redis_url: str
celery_broker_url: str
celery_result_backend: Optional[str] = None
model_config = SettingsConfigDict(
env_prefix="",
case_sensitive=False,
extra="ignore",
)
@lru_cache
def get_settings() -> Settings:
"""
获取配置(带缓存)。
加载顺序:
- 优先使用 `.env.dev/.env.prod`(若存在)
- 否则使用系统环境变量
"""
import os
env = os.getenv("APP_ENV", "dev").strip() or "dev"
env_file = _guess_env_file(env)
return Settings(_env_file=env_file)

6
server/app/db/base.py Normal file
View File

@@ -0,0 +1,6 @@
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
"""SQLAlchemy ORM Base。"""

36
server/app/db/session.py Normal file
View File

@@ -0,0 +1,36 @@
from __future__ import annotations
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.core.config import get_settings
def create_engine():
"""
创建异步 Engine。
注意:连接串从环境变量 `DATABASE_URL` 读取。
"""
settings = get_settings()
return create_async_engine(settings.database_url, pool_pre_ping=True)
engine = create_engine()
AsyncSessionLocal: async_sessionmaker[AsyncSession] = async_sessionmaker(
bind=engine,
expire_on_commit=False,
autoflush=False,
autocommit=False,
)
async def get_db() -> AsyncSession:
"""
FastAPI 依赖:获取数据库会话。
"""
async with AsyncSessionLocal() as session:
yield session

25
server/app/main.py Normal file
View File

@@ -0,0 +1,25 @@
from fastapi import FastAPI
from app.core.config import get_settings
def create_app() -> FastAPI:
"""
创建 FastAPI 应用实例。
说明目前仅提供最小可运行骨架health check后续逐步加入路由与中间件。
"""
settings = get_settings()
app = FastAPI(title=settings.app_name)
@app.get("/healthz")
async def healthz() -> dict:
return {"status": "ok", "env": settings.app_env}
return app
app = create_app()

View File

@@ -0,0 +1,4 @@
"""
Celery 任务集合。
"""

13
server/app/tasks/ping.py Normal file
View File

@@ -0,0 +1,13 @@
from celery import shared_task
@shared_task(name="tasks.ping")
def ping(msg: str = "pong") -> str:
"""
示例任务:用于验证 Celery worker 可消费任务。
注意:任务参数请尽量保持小(只传 id/短文本),避免 Redis 队列膨胀。
"""
return msg

43
server/app/worker.py Normal file
View File

@@ -0,0 +1,43 @@
from __future__ import annotations
from celery import Celery
from app.core.config import get_settings
def _env_prefix(app_env: str) -> str:
"""
根据环境生成前缀:
- dev -> dev
- prod -> pro
说明Redis ACL 限制使用 `dev:*` / `pro:*`。
"""
return "dev" if app_env == "dev" else "pro"
settings = get_settings()
prefix = _env_prefix(settings.app_env)
# 关键:使用 Redis transport 的全局 key 前缀,确保所有 broker key 都在 ACL 允许范围内
broker_transport_options = {"global_keyprefix": f"{prefix}:"}
celery_app = Celery(
"mindfulness",
broker=settings.celery_broker_url,
backend=settings.celery_result_backend,
broker_transport_options=broker_transport_options,
)
# 默认不存结果(降低 Redis 占用)。如需结果存储,可在业务中显式开启并设置 TTL。
celery_app.conf.update(
task_ignore_result=True if not settings.celery_result_backend else False,
task_default_queue=f"{prefix}:celery",
task_default_exchange=f"{prefix}:celery",
task_default_routing_key=f"{prefix}:celery",
)
# 自动发现任务app/tasks 下的 shared_task
celery_app.autodiscover_tasks(["app.tasks"])