重构前保存现有 apps/ 结构基线,供后续各阶段回滚。 Co-Authored-By: Claude <noreply@anthropic.com>
8.6 KiB
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
# 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_demodecorators. ReturnsR.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. Usesdb.query(Model)directly with SQLAlchemy. Reads query params viarequest.query_params; body params arrive as already-validated Pydantic models, so services access fields viadata.fieldordata.model_dump()(noawait request.json()in service bodies).forms/{module}.py- PydanticBaseModelvalidation classes. Fields useField(...)for constraints (min_length,max_length,ge,le,gt). Custom validators use@field_validator('field')raisingValueError(msg). Uniqueness checks querydb.query(Model).filter(...).first()inside validators.models/{module}.py- SQLAlchemy ORM model inheritingbase_model(provides id, create_user, create_time, update_user, update_time, is_delete) andbase_db(provides save(), save_all(), delete()). Table name usesDB_PREFIXprefix.
Request Validation
Pydantic v2 is used for all request body validation. The pattern for an endpoint:
@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 apps/__init__.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.envviapython-dotenv.config/__init__.pyloads.envfirst, then re-exports everything.extensions/- Framework extensions: SQLAlchemy engine/session/contextvars (extensions/sqlalchemy.py), custom exception class (extensions/exception.py),get_db_session()accessor.routers/v1.py- Central route registration. All module routers are included here with prefix paths.apps/middleware/-login_required(JWT auth dependency, ignores/loginand/captcha),permission_required(decorator checking user's permission nodes; admin userId=1 bypasses all checks),check_demo(decorator that blocks write operations whenFASTAPI_DEMO=True),db_session(Starlette middleware setting request-scoped DB session via contextvars).utils/-response(JSON response helpers:R.ok(),R.failed()),jwt(JWT create/parse/refresh/blacklist with HS256),password(bcrypt-based password hashing with salt),security(get_user_id(request),get_username(request)— reads fromrequest.stateset bylogin_required),request(parse_batch_ids,like_escape,like_containsfor safe LIKE queries),captcha(verification code generation),file(file operations),redis_ctx(Redis connection pool lifecycle and request-scoped context viaget_redis()),ip,formatter,rich_text.apps/constants/-constants.py(enum dicts for gender, dept types, menu types, etc.),message.py(default page size, messages,SERIALIZE_EXCLUDE_FIELDS).
Database Access
Database sessions are request-scoped via contextvars + Starlette middleware (apps/middleware/db_session.py). Services call db = get_db_session() to get the current request's session. Models use self.save() / self.delete() from base_db. 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 utils/redis_ctx.py. Connection pool created in redis_lifespan (app 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
utils/jwt.py(HS256, configurable expiry via JWT_EXPIRE_MINUTES, secret via JWT_SALT env var). Token sent asAuthorization: Bearer <token>header. Supports token refresh and blacklist (logout) via Redis. login_requiredis a FastAPIDependsapplied globally to the v1 router. On success, it setsrequest.state.user_id,request.state.username,request.state.realname.permission_requiredis a per-endpoint decorator checking permission strings like"sys:level:list". User ID 1 (admin) bypasses all permission checks.check_demois a per-endpoint decorator that returnsR.failed("演示环境,暂无操作权限")whenFASTAPI_DEMO=True. Apply it after@permission_requiredon all write endpoints (add, update, delete, status, import, export).
API Response Format
All endpoints return JSONResponse via R.ok() or R.failed():
{"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
- Create
apps/models/{name}.py- model class inheritingbase_modelandbase_db, set__tablename__ = DB_PREFIX + "{name}" - Create
apps/forms/{name}.py- PydanticBaseModelvalidation classes withField(...)constraints and@field_validatorfor custom checks - Create
apps/services/{name}.py- CRUD service functions following theget_level_page/get_level_detail/add_level/update_level/delete_level/update_level_statuspattern - Create
apps/views/{name}.py- APIRouter with endpoints, apply@permission_requiredand@check_demoon write endpoints - Register the router in
routers/v1.py-v1.include_router({name}.router, prefix="/{prefix}", tags=["标签"]) - Import the view in
routers/v1.py
Key Dependencies
- FastAPI 0.140.0, Uvicorn 0.51.0
- SQLAlchemy 2.0.51 (declarative style with
declarative_base(), not 2.0mapped_columnstyle) - 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
.envloading, bcrypt for password hashing, Pillow for captcha generation