# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview DjangoAdmin FastAPI+EleVue is a backend admin framework built with FastAPI (Python), Vue2.x + ElementUI (frontend, separate repo), and MySQL. It provides RBAC-based user/role/menu management, dictionary/config management, and CMS-like modules (notices, ads, members, etc.). The UI language and code comments are in Chinese. ## Running the Application ```bash # Install dependencies pip install -r requirements.txt # Run dev server (default: 127.0.0.1:8031) python main.py # Run via uvicorn directly uvicorn main:app --host 127.0.0.1 --port 8031 # Docker docker-compose up --build ``` Requires MySQL and Redis running. Configure connection details in `.env` (see `.env` for all variables). The database table prefix is `fastapi_` (set via `DB_PREFIX`). No test suite, linter, or formatter configuration exists in this project. ## Architecture ### Request Flow `main.py` -> `apps.create_app()` -> registers CORS, Redis, routers, middleware, exception handlers -> all routes go through `login_required` dependency (except `/login` and `/captcha`) -> `permission_required` decorator checks node-level RBAC per endpoint -> `check_demo` decorator blocks write operations in demo mode. ### Layered Module Pattern Each business module (e.g., level, position, dept, role, user, menu, notice, dicts, config, etc.) follows the same 4-file pattern under `apps/`: - **`views/{module}.py`** - FastAPI router with endpoint definitions. Uses `@permission_required("sys:{module}:{action}")` and `@check_demo` decorators. Returns `R.ok()`/`R.failed()`. Endpoints that accept a body declare a Pydantic form parameter (e.g., `data: PositionForm`) — FastAPI parses and validates the JSON automatically before the handler runs. - **`services/{module}.py`** - Business logic: queries, CRUD operations, Excel import/export. Uses `db.query(Model)` directly with SQLAlchemy. Reads query params via `request.query_params`; body params arrive as already-validated Pydantic models, so services access fields via `data.field` or `data.model_dump()` (no `await request.json()` in service bodies). - **`forms/{module}.py`** - Pydantic `BaseModel` validation classes. Fields use `Field(...)` for constraints (`min_length`, `max_length`, `ge`, `le`, `gt`). Custom validators use `@field_validator('field')` raising `ValueError(msg)`. Uniqueness checks query `db.query(Model).filter(...).first()` inside validators. - **`models/{module}.py`** - SQLAlchemy ORM model inheriting `base_model` (provides id, create_user, create_time, update_user, update_time, is_delete) and `base_db` (provides save(), save_all(), delete()). Table name uses `DB_PREFIX` prefix. ### Request Validation Pydantic v2 is used for all request body validation. The pattern for an endpoint: ```python @router.post('/add') @permission_required("sys:level:add") @check_demo async def add(request: Request, data: LevelForm): return await level.LevelAdd(request, data) ``` Validation errors are caught by a global `RequestValidationError` handler registered in `core/app.py` and converted to the standard response shape `{"code": -1, "data": null, "msg": "field: message/..."}` with HTTP 200. Services receive the validated Pydantic instance and use `data.model_dump()` (full dict), `data.model_dump(exclude={'id'})` (for updates), or direct attribute access (`data.id`, `data.status`). ### Key Directories - **`config/`** - Configuration split into sub-modules: `app.py` (app settings, CORS, upload limits), `database.py` (DB connection, pool config, `SQLALCHEMY_MYSQL_URL`), `redis.py`, `auth.py` (JWT), `captcha.py`, `email.py`, `sms.py`. All loaded from `.env` via `python-dotenv`. `config/__init__.py` loads `.env` first, then re-exports everything. - **`core/`** - Core infrastructure: `database.py` (engine/SessionLocal/`_db_ctx`/`get_db_session()`/`commit_independently`/`Base`), `redis_client.py` (`get_redis()`/`redis_lifespan`/`redis_middleware`), `jwt.py` (JWT create/parse/refresh/blacklist with HS256), `password.py` (bcrypt hashing with salt), `response.py` (`R.ok()`/`R.failed()`), `exceptions.py` (AuthorizationException/BusinessException), `security.py` (`get_user_id`/`get_username`), `middleware.py` (`login_required`/`permission_required`/`check_demo`/`db_session_middleware`/`operation_log_middleware`/`register_cros`), `deps.py` (DI), `logger.py` (structured logging), `app.py` (create_app factory + lifespan). - **`shared/`** - Cross-module shared layer: `enums.py` (enum dicts), `constants.py` (PAGE_SIZE/DEFAULT_PASSWORD/SERIALIZE_EXCLUDE_FIELDS), `pagination.py`, `base_model.py`, `base_db.py`, `base_repository.py` (Generic repository base), `base_service.py`. - **`modules/`** - Business modules, each a directory with `router.py` (may export multiple routers, e.g. `dict_router` + `dict_item_router`), `schemas.py`, `service.py`, `repository.py`, `models.py`. Module groups: `auth/`, `system/` (user, role, menu, dept, position, level), `dictionary/`, `configuration/`, `cms/` (item, item_cate, ad, ad_sort), `member/`, `region/`, `link/`, `notice/`, `job/` (job + job_log + scheduler + job_executor), `log/`, `upload/`, `index/`. - **`routers/`** - `v1.py` central route registration (imports routers from `modules/`), `__init__.py` mounts login (免认证) on app and v1 behind `login_required`. - **`utils/`** - Remaining utilities that stay here: `request` (`parse_batch_ids`, `like_escape`, `like_contains`), `captcha`, `file`, `ip`/`ip2region`, `ua`, `rich_text`, `perm_cache`. `R`/`password` are re-exported from `core` via `utils/__init__.py` for back-compat. ### Database Access Database sessions are request-scoped via `contextvars` + Starlette middleware (`core/middleware.py: db_session_middleware`). Services call `db = get_db_session()` (from `core.database`) to get the current request's session. Models use `self.save()` / `self.delete()` from `shared/base_db.py`. The middleware auto-closes the session after each request (with rollback). Soft deletes via `is_delete` flag (0=active, 1=deleted). ### Redis Access Redis uses async client via `core/redis_client.py`. Connection pool created in `redis_lifespan` (composed with scheduler startup in `core/app.py` lifespan), injected per-request via `redis_middleware` into contextvars. Business code calls `get_redis()` to get the current request's async Redis client. ### Authentication & Authorization - JWT tokens via `core/jwt.py` (HS256, configurable expiry via JWT_EXPIRE_MINUTES, secret via JWT_SALT env var). Token sent as `Authorization: Bearer ` header. Supports token refresh and blacklist (logout) via Redis. - `login_required` is a FastAPI `Depends` applied globally to the v1 router. On success, it sets `request.state.user_id`, `request.state.username`, `request.state.realname`. - `permission_required` is a per-endpoint decorator checking permission strings like `"sys:level:list"`. User ID 1 (admin) bypasses all permission checks. It lazily queries `modules.system.menu.service.get_permissions_list_sync`. - `check_demo` is a per-endpoint decorator that returns `R.failed("演示环境,暂无操作权限")` when `FASTAPI_DEMO=True`. Apply it after `@permission_required` on all write endpoints (add, update, delete, status, import, export). ### API Response Format All endpoints return `JSONResponse` via `R.ok()` or `R.failed()`: ```json {"code": 0, "data": ..., "msg": "操作成功", "ok": true} // success {"code": -1, "data": null, "msg": "错误信息", "ok": false} // failure ``` `R.ok()` supports `**kwargs` for extra fields (e.g., `R.ok(data=result, count=total)` adds a `count` field). ### Pagination Pattern List endpoints read `pageNo` and `pageSize` from `request.query_params`, defaulting to page 1 and `PAGE_SIZE` (10). Services use SQLAlchemy `.limit(limit).offset((page - 1) * limit).all()` and return `R.ok(data=result, count=count)`. ### Delete Pattern Single delete: `DELETE /delete/{id}` where id can be comma-separated (e.g., `"1,2,3"`). Batch delete: `DELETE /batchDelete` with JSON body `[1, 2, 3]`, parsed via `parse_batch_ids(request)`. Both use soft delete (set `is_delete = 1`). ## Adding a New Module 1. Create `modules/{group}/{name}/models.py` - model class inheriting `base_model` and `base_db` (from `shared/`), set `__tablename__ = DB_PREFIX + "{name}"` 2. Create `modules/{group}/{name}/schemas.py` - Pydantic `BaseModel` validation classes with `Field(...)` constraints and `@field_validator` for custom checks 3. Create `modules/{group}/{name}/repository.py` - subclass `shared.base_repository.BaseRepository` + module-level instance 4. Create `modules/{group}/{name}/service.py` - CRUD service functions following the `get_level_page`/`get_level_detail`/`add_level`/`update_level`/`delete_level`/`update_level_status` pattern 5. Create `modules/{group}/{name}/router.py` - APIRouter with endpoints, apply `@permission_required` and `@check_demo` on write endpoints 6. Register the router in `routers/v1.py` - `v1.include_router({name}_router, prefix="/{prefix}", tags=["标签"])` ## Key Dependencies - FastAPI 0.140.0, Uvicorn 0.51.0 - SQLAlchemy 2.0.51 (declarative style with `declarative_base()`, not 2.0 `mapped_column` style) - PyMySQL 1.2.0, Redis 8.0.1 (async), PyJWT 2.13.0 - Pydantic 2.13.4 (for request validation via BaseModel + Field + field_validator), openpyxl (for Excel import/export) - python-dotenv for `.env` loading, bcrypt for password hashing, Pillow for captcha generation