# VideoGen 代码架构指南 > 本文档面向开发者或 AI 模型,帮助快速理解代码风格、架构约定和修改规则。 --- ## 一、整体架构 ``` video_item/ ├── video-gen-api/ # 后端 (Python FastAPI) ├── video-gen-app/ # 前台/用户端 (React + Vite) ──┤ └── video-gen-admin/ # 后台管理端 (React + Vite) ──┘ 两者共用同一个后端 API ``` **核心原则:** 三个项目完全分离,前台和后台是两个独立的 SPA,部署在不同的子域名上,但调用同一套后端接口。 --- ## 二、后端架构 (video-gen-api) ### 2.1 目录结构 ``` app/ ├── api/ # API 路由层(薄层,仅做参数解析和调用 service) │ ├── v1/ # 前台 + 部分管理接口(FastAPI 意义上的 v1 版本) │ │ ├── __init__.py # 聚合所有 v1 router │ │ ├── auth.py │ │ ├── projects.py │ │ ├── generation.py │ │ ├── admin.py # prefix="/admin"(管理接口混入 v1) │ │ └── ... │ └── admin/ # 额外的管理端细分路由 │ ├── __init__.py │ ├── team.py │ ├── home_material.py │ └── ... ├── schemas/ # Pydantic 模型(请求入参 + 响应序列化) │ ├── common.py # 公共基类:NaiveDatetime, PaginatedResponse │ ├── generation.py │ ├── auth.py │ └── ... ├── models/ # SQLAlchemy ORM 模型(41 个) │ ├── base.py # Base, TimestampMixin, SoftDeleteMixin │ ├── user.py │ └── ... ├── services/ # 业务逻辑层(核心业务写在这里) │ ├── auth.py # JWT 认证、密码哈希 │ ├── credits.py # 积分扣减 │ ├── operation_log.py # 操作日志 │ └── ... ├── enums/ # 枚举定义(全部用 str, Enum) │ ├── generation_status.py │ ├── celery_queue.py │ └── ... ├── tasks/ # Celery 异步任务 │ ├── celery_app.py # Celery 实例 + 配置 │ ├── cleanup.py │ └── ... ├── middleware/ # FastAPI 中间件 │ ├── logging.py │ ├── rate_limit.py │ ├── request_encrypt.py │ ├── anti_crawler.py │ └── ... ├── utils/ # 通用工具 │ ├── id_gen.py # generate_id(), generate_order_no() │ ├── exceptions.py # 自定义 HTTPException │ └── security.py # AES 加密、HMAC ├── dependencies.py # FastAPI 依赖注入(get_db, get_current_user 等) ├── config.py # Pydantic Settings,读取 .env └── main.py # FastAPI 应用入口、lifespan、种子数据 ``` ### 2.2 分层约定(重要) 请求处理严格遵循 **路由 → 服务 → 模型** 三层: ``` Route (api/) → Service (services/) → Model (models/) 解析参数 业务逻辑 数据库操作 调用 service 编排调用 纯 CRUD 返回序列化结果 不直接操作 ORM 无业务规则 ``` **禁止:** - 路由中直接写复杂 SQL/ORM 操作 → 应抽到 service - Model 文件中写业务逻辑 → model 只定义字段和关系 - Service 中混入 HTTP 相关代码 → 不 import FastAPI 对象 ### 2.3 SQLAlchemy 模型约定 使用 SQLAlchemy 2.0 风格的 `mapped_column`: ```python # app/models/project.py — 标准模板 from sqlalchemy import ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base, TimestampMixin, SoftDeleteMixin class Project(Base, TimestampMixin, SoftDeleteMixin): __tablename__ = "projects" id: Mapped[str] = mapped_column(String(32), primary_key=True) user_id: Mapped[str] = mapped_column(String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True) name: Mapped[str] = mapped_column(String(128)) industry: Mapped[str] = mapped_column(String(32)) ``` **约定:** - 主键统一用 `String(32)`,由 `generate_id()` 生成(13 位时间戳 hex + 6 位随机 hex) - 继承 `TimestampMixin` 自动获得 `created_at` / `updated_at` - 需要软删除的继承 `SoftDeleteMixin` 获得 `deleted_at` - 外键统一加 `index=True`,`ondelete="CASCADE"` 按需 - 所有 `str` 类型指定长度:`String(32)` / `String(128)` / `String(255)` 等 - 时间统一用 `DateTime(timezone=True)` - **新增 model 后**必须在 `app/models/__init__.py` 中 import ```python # app/models/base.py — 公共 Mixin class TimestampMixin: created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) class SoftDeleteMixin: deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) ``` ### 2.4 Pydantic Schema 约定 ```python # app/schemas/project.py from pydantic import BaseModel, Field from app.schemas.common import NaiveDatetime class ProjectCreate(BaseModel): # 请求入参(客户端 → 服务端) name: str = Field(..., max_length=128) industry: str = Field(..., max_length=64) class ProjectOut(BaseModel): # 响应序列化(服务端 → 客户端) id: str name: str industry: str created_at: NaiveDatetime updated_at: NaiveDatetime model_config = {"from_attributes": True} # ← 必加!允许从 ORM 对象直接构造 ``` **命名约定:** - `XxxCreate` — POST 创建请求 - `XxxUpdate` — PUT 更新请求 - `XxxOut` / `XxxResponse` — 响应 - `XxxListOut` — 列表响应(含 items + total) - `XxxRequest` — 通用请求 **公共工具:** - `NaiveDatetime` / `NaiveDatetimeOptional` — 自动将带时区的 datetime 转为北京时间 naive datetime - `PaginatedResponse` — 分页响应基类 ### 2.5 API 路由约定 ```python # app/api/v1/projects.py — 标准模板 from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, get_current_user from app.models.user import User from app.models.project import Project from app.schemas.project import ProjectCreate, ProjectOut from app.utils.id_gen import generate_id router = APIRouter(prefix="/projects", tags=["projects"]) # prefix 不含 /api @router.get("", response_model=list[ProjectOut]) # GET 列表用 list[XxxOut] async def list_projects( current_user: User = Depends(get_current_user), # 鉴权依赖 db: AsyncSession = Depends(get_db), # 数据库 session ): result = await db.execute( select(Project).where( Project.user_id == current_user.id, Project.deleted_at.is_(None), # 软删除过滤 ).order_by(Project.created_at.desc()) ) return result.scalars().all() # 直接返回 ORM 对象,Pydantic 自动序列化 @router.post("", response_model=ProjectOut) async def create_project( req: ProjectCreate, # 请求体自动校验 current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): project = Project( id=generate_id(), # 主键手工生成 user_id=current_user.id, name=req.name, industry=req.industry, ) db.add(project) await db.flush() # flush 获取自增字段但不出事务 return project ``` **关键约定:** - `prefix` 不含 `/api`(`/api` 在 `main.py` 的 `include_router` 统一加) - 受保护接口用 `Depends(get_current_user)` 获取当前用户 - 管理员接口用 `Depends(get_admin_user)` - 可选登录用 `Depends(get_optional_current_user)` - 查询过滤软删除: `.where(Model.deleted_at.is_(None))` - 返回 ORM 对象时 Schema 必须有 `model_config = {"from_attributes": True}` - 错误抛 `HTTPException` 或用自定义异常(见 `app/utils/exceptions.py`) ### 2.6 依赖注入约定 ```python # app/dependencies.py get_db # 生成 AsyncSession,自动 commit/rollback get_current_user # 验证 JWT → 返回 User,要求密码已设置 get_current_user_allow_password_pending # 验证 JWT → 返回 User(允许未设置密码) get_optional_current_user # 验证 JWT → User | None(不强制登录) get_admin_user # 验证 JWT + is_admin + user_type=="admin" get_backend_user # 验证 JWT + user_type=="admin"(不要求 is_admin) ``` ### 2.7 服务层约定 ```python # app/services/team_service.py — 标准模板 from __future__ import annotations # 前向引用必需的 import from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.team import Team from app.schemas.team import TeamCreate, TeamUpdate from app.utils.id_gen import generate_id async def create_team(db: AsyncSession, req: TeamCreate) -> Team: team = Team( id=generate_id(), name=req.name, code=req.code, description=req.description, ) db.add(team) await db.flush() return team ``` **约定:** - 函数签名统一 `(db: AsyncSession, ...) → Model | list[Model] | dict` - 不处理 HTTP 异常,HTTP 相关处理留给 route 层 - 用 `from __future__ import annotations` 支持前向引用 - 纯查询类服务可以接收 `db` 作为第一个参数 ### 2.8 枚举约定 ```python # app/enums/generation_status.py from enum import Enum class GenerationStatus(str, Enum): # 继承 str 使值可直接序列化 prompt_optimized = "prompt_optimized" generating = "generating" completed = "completed" failed = "failed" class GenerationType(str, Enum): video = "video" image = "image" ``` **约定:** - 所有枚举继承 `str, Enum`(可序列化为 JSON) - 枚举值用 snake_case - 枚举文件统一放 `app/enums/` - 在 schema 中引用枚举做校验: `gen_type: GenerationType = Field(...)` ### 2.9 异常处理约定 ```python # app/utils/exceptions.py — 预定义异常 InsufficientCreditsError # 402 积分不足 CaptchaFailedError # 400 验证码失败 RecordNotFoundError # 404 记录不存在 ProjectNotFoundError # 404 项目不存在 InvalidStatusError # 400 状态不允许操作 # 在 service 或 route 中抛出 raise InsufficientCreditsError() ``` ### 2.10 认证约定 ```python # Token 创建 create_access_token(user_id, remember_me=False) → str # JWT 令牌 # Token 解码 decode_access_token(token) → str | None # 返回 user_id 或 None # 密码 hash_password(plain) → str # bcrypt 哈希 verify_password(plain, hashed) → bool # bcrypt 校验 ``` **鉴权流程:** 1. 前端 Authorization header: `Bearer ` 2. `HTTPBearer` 提取 token → `decode_access_token` 获取 `user_id` 3. 查 User 表验证 `is_active` 4. 检查 `user_must_set_password`(短信注册用户首次需设置密码) ### 2.11 数据库 Session 模式 ```python # dependencies.py 中的 get_db — 自动管理事务 async def get_db(): async with async_session() as session: try: yield session await session.commit() # 成功自动提交 except Exception: await session.rollback() # 异常自动回滚 raise finally: await session.close() ``` **注意:** 在 route 中如果只读查询不需要手动 commit(get_db 自动处理)。在 service 中做写入时需要调用者负责 commit,或由 get_db 处理。 ### 2.12 中间件约定 中间件按**注册顺序**从外到内执行(`main.py`): ```python application.add_middleware(RequestLoggingMiddleware) # 1. 请求日志 application.add_middleware(AntiCrawlerMiddleware) # 2. 反爬虫 application.add_middleware(RateLimitMiddleware) # 3. 限流 (Redis) application.add_middleware(RequestEncryptMiddleware) # 4. 加密/解密 application.add_middleware(CORSMiddleware, ...) # 5. CORS ``` **加密中间件行为:** - 请求有 `X-Encrypted: true` header → 解密请求体 - GET 请求无 body 但**响应仍加密** - 支付回调路径白名单跳过加密 (`/payments/alipay/callback`, `/payments/wechat/callback`) ### 2.13 Celery 任务约定 ```python # app/tasks/cleanup.py — 标准模板 from app.tasks.celery_app import celery_app @celery_app.task # 装饰器注册任务 def cleanup_expired_video_urls(): """Run hourly. Clear expired video URL tokens.""" asyncio.run(_cleanup_urls()) # 同步任务内跑异步代码 async def _cleanup_urls(): # 实际逻辑写在 async 函数里 from app.models.base import async_session # 延迟导入避免循环 async with async_session() as db: ... await db.commit() ``` **约定:** - 任务装饰器: `@celery_app.task` - 同步入口 → `asyncio.run()` 包异步逻辑 - 任务名自动生成: `文件名.函数名`(如 `app.tasks.cleanup.cleanup_expired_video_urls`) - 队列路由在 `celery_app.py` 的 `task_routes` 配置 - 必需参数通过 `apply_async(args=[...], queue="xxx", priority=0)` 传递 ### 2.14 配置约定 ```python # app/config.py — 基于 pydantic-settings from pydantic_settings import BaseSettings class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") DATABASE_URL: str = "sqlite+aiosqlite:///./videogen.db" SECRET_KEY: str = "change-me" # ... 所有配置项有默认值,生产通过 .env 覆盖 settings = Settings() # 全局单例 ``` **约定:** - 所有环境变量在 `config.py` 中有类型注解和默认值 - 通过 `settings.XXX` 全局访问 - 布尔值用 `bool` 类型(pydantic 自动解析 "true"/"false" 字符串) - 列表值用 `list[str]`(pydantic 自动解析 JSON 数组字符串) ### 2.15 操作日志约定 ```python from app.services.operation_log import log_operation # 在路由中调用(通常在变更操作后) await log_operation( db, admin.id, admin.username, f"创建团队 {team.name}", # action 描述 "POST", # HTTP method "/admin/teams", # path detail=None, # 可选详情 ip=None, # 可选 IP ) ``` --- ## 三、前端架构 (video-gen-app & video-gen-admin) ### 3.1 相同点 两个前端项目遵循完全相同的架构约定: - React 19 + TypeScript + Vite 8 + Ant Design 6 + Tailwind CSS 3 - Zustand 状态管理 - 统一的 API 调用层(crypto/client/index 三层) - 路由结构 SPA(react-router-dom v7) ### 3.2 目录结构(以 video-gen-app 为例) ``` src/ ├── api/ # API 调用层 │ ├── client.ts # 核心:apiRequest(加密/解密、错误处理) │ ├── crypto.ts # AES-GCM 加密/解密(Web Crypto API) │ ├── index.ts # 所有业务 API 函数(按业务域分组) │ └── mock.ts # Mock 数据(VITE_USE_MOCK=true 时使用) ├── store/ # Zustand 状态 │ ├── useAuthStore.ts # 认证状态(登录/登出/当前用户) │ └── useAppStore.ts # 应用状态 ├── types/ # TypeScript 类型定义 │ └── index.ts # 所有 interface/type ├── pages/ # 页面组件(路由对应) │ ├── LoginPage.tsx │ ├── ProjectsPage.tsx │ └── ... ├── components/ # 可复用组件 │ ├── Layout/ │ │ ├── AppLayout.tsx # 主布局(Header + Sider + Content) │ │ └── AppLayout.css │ ├── privatePortrait/ │ └── ... ├── utils/ # 工具函数 │ ├── formatDate.ts │ └── ... ├── App.tsx # 路由定义 + 全局配置 └── main.tsx # 入口 ``` ### 3.3 API 调用约定(核心) ```typescript // ── client.ts — 核心请求函数 ── // 所有请求统一走 apiRequest,自动处理加密和响应解析 async function apiRequest(path: string, options: RequestOptions): Promise // 调用参数: interface RequestOptions { method?: string // GET/POST/PUT/DELETE,默认 GET body?: unknown // 请求体(自动 JSON 序列化 + 加密) auth?: boolean // 是否带 Authorization header,默认 true encryptBody?: boolean // 是否加密,默认跟随全局开关 signal?: AbortSignal // 取消请求 skipAuthRedirect?: boolean // 401 时不跳转登录页 } ``` ```typescript // ── index.ts — 业务 API 函数 ── // 命名约定:动词 + 业务名,camelCase export async function login(username: string, password: string): Promise { if (USE_MOCK) return mock.mockLogin({ username, password }); const res = await api.post<{ accessToken: string; user: User }>( '/auth/login', { username, password }, false, // auth: false(登录不需要 token) true // encryptBody: true(登录凭证需要加密) ); setToken(res.accessToken); return res.user; } // GET 列表 export async function getProjects(): Promise { if (USE_MOCK) return mock.mockGetProjects(); return api.get('/projects'); } // POST 创建 export async function createProject(name: string, industry: Industry): Promise { if (USE_MOCK) return mock.mockCreateProject(name, industry); return api.post('/projects', { name, industry }); } // 带查询参数的 GET export async function getRecordsPage(params): Promise { const query = new URLSearchParams(); if (params.projectId) query.set('project_id', params.projectId); query.set('page', String(page)); return api.get(`/generation-records?${query.toString()}`); } ``` **关键约定:** - `api.get/post/put/delete(path, body?, auth?, encryptBody?)` — 返回**已解析的 T 类型数据**(不含响应包裹层) - 后端返回 `{ data: ... }` 的结构,`apiRequest` 会自动解包 - 所有 id 字段前端用 `string`(不转 number) - 后端 `snake_case` 字段在 `client.ts` 自动转 `camelCase`(user_id → userId) - 文件上传不走 `apiRequest`,直接用 `fetch` + `FormData`(见 `uploadAudio/uploadImage` 等) - 上传相关 API 直接读 `import.meta.env.VITE_API_BASE` 拼 URL ### 3.4 状态管理约定(Zustand) ```typescript // store/useAuthStore.ts — 标准模板 import { create } from 'zustand'; import type { User } from '../types'; import * as api from '../api'; interface AuthState { user: User | null; loading: boolean; login: (username: string, password: string) => Promise; logout: () => Promise; checkAuth: () => Promise; // 启动时验证 token 有效性 } export const useAuthStore = create((set) => ({ user: null, loading: true, login: async (username, password) => { const user = await api.login(username, password); set({ user }); }, // ... })); ``` **约定:** - 全局状态用 Zustand `create`,不用 Redux - API 调用写在 store actions 或页面中,**不要**写在 components 里 - 状态更新用 `set()`,异步操作加 `async/await` - 组件中消费: `const { user, login } = useAuthStore()` ### 3.5 页面组件约定 ```typescript // pages/ProjectsPage.tsx — 标准模板 import React, { useEffect, useState } from 'react'; import { Button, Empty, Form, Input, message, Modal, Typography } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { useAppStore } from '../store/useAppStore'; import { getIndustries } from '../api'; import type { IndustryConfig } from '../types'; const ProjectsPage: React.FC = () => { const navigate = useNavigate(); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const loadProjects = async () => { setLoading(true); try { const data = await getProjects(); // 调 API setProjects(data); } catch (e: any) { message.error(e?.message || '加载失败'); } finally { setLoading(false); } }; useEffect(() => { loadProjects(); }, []); return (
我的项目 {/* ... */}
); }; export default ProjectsPage; ``` **约定:** - 页面组件是 `React.FC`,默认导出 - 业务数据类型从 `../types` 导入(`import type`) - API 函数从 `../api` 导入 - 不用 `axios`,所有请求走 `api/index.ts` - 用 `message.success/error/warning` 显示操作反馈 - 用 `useNavigate()` 编程式导航 - 列表加载用 `loading` 状态 + `Spin` 组件 - 错误统一 `catch (e: any)` + `message.error(e?.message || '默认消息')` ### 3.6 路由约定 ```typescript // App.tsx — 路由结构 // 公开路由 } /> } /> // 受保护路由(需要登录) }> } /> } /> } /> } /> ``` **约定:** - 路由定义集中在 `App.tsx` - 登录保护通过 `ProtectedRoute` 包装器实现(检查 `useAuthStore.user`) - 未登录用户访问受保护路由 → 跳 `/login` - 动态路由参数用 `:paramName` - 嵌套路由用 `` 渲染子页面 ### 3.7 TypeScript 类型约定 ```typescript // types/index.ts — 全局类型定义 export interface User { id: string; // 所有 ID 都是 string username: string; credits: number; // ... } ``` **约定:** - 全局共享类型在 `types/index.ts` 定义 - 类型/接口名 PascalCase + 业务含义 - 所有 ID、外键字段类型为 `string` - 可选字段用 `?:`,不可选但不一定传的用联合类型 `string | null` - 从其他文件导入类型: `import type { ... } from '../types'`(用 `import type` 优化打包) ### 3.8 加密约定 ```typescript // crypto.ts const USE_ENCRYPTION = !!import.meta.env.VITE_ENCRYPTION_KEY && isCryptoAvailable(); // 前端加密流程: // 1. apiRequest 中如果 encryptBody=true: // body → JSON.stringify → encrypt() → { data: "<密文>" } // headers['X-Encrypted'] = 'true' // 2. 收到响应如果 encryptBody=true 且 parsed.data 是字符串: // decrypt(parsed.data) → JSON.parse → 返回 // // 自动判断: USE_ENCRYPTION 全局开关控制是否启用加密 // 加密算法: AES-256-GCM (12 字节 IV + 128 位认证标签) // 密钥来源: VITE_ENCRYPTION_KEY (32 字节 base64) ``` ### 3.9 Mock 模式约定 当 `VITE_USE_MOCK=true` 时,前端走本地假数据,无需后端。 ```typescript // api/index.ts 中每个 API 函数都检查 USE_MOCK const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true'; export async function getProjects(): Promise { if (USE_MOCK) return mock.mockGetProjects(); // ← 先看 mock return api.get('/projects'); // ← 后走真实请求 } ``` **新增 API 函数时**必须同时提供 mock 数据函数(在 `mock.ts` 中)。 --- ## 四、跨项目约定(前后端协作) ### 4.1 字段名转换 | 后端 (Python) | 前端 (TypeScript) | 转换 | |--------------|-------------------|------| | `snake_case` | `camelCase` | client.ts 自动转 | | `created_at` | `createdAt` | 自动 | | `user_id` | `userId` | 自动 | **新增 API 时**: - 后端 request schema 字段用 `snake_case` - 前端 types/interface 字段用 `camelCase` - **不需要**手动转,`client.ts` 的 `keysToCamel()` 递归转换所有 key ### 4.2 API 路径约定 ``` /api/auth/login → 登录(公开) /api/projects → 项目管理(需登录) /api/admin/users → 用户管理(需管理员) /admin/teams → 团队管理(需管理员,二级路由) ``` - `/api/` 前缀统一 - 管理接口路径含 `/admin/` - 路径用小写 + 短横线(kebab-case) ### 4.3 分页约定 后端返回: ```python @router.get("", response_model=GenerationRecordPageListOut) # { items: [...], total: 100, page: 1, page_size: 20 } ``` 前端类型: ```typescript export interface GenerationRecordPageListOut { page: number; pageSize: number; total: number; items: GenerationRecord[]; } ``` ### 4.4 错误处理约定 后端抛 `HTTPException(status_code, detail)` → 前端 `apiRequest` 捕获后抛 `Error` → 页面 `catch` + `message.error(e.message)`。 常见错误码: - `401` — token 无效/过期 → 自动跳 `/login` - `403` — 权限不足 - `404` — 资源不存在 - `402` — 积分不足(`InsufficientCreditsError`) - `400` — 参数错误 --- ## 五、修改检查清单 ### 新增后端接口时 ✅ - [ ] 在 `app/schemas/` 中定义 Request/Response Schema(含 `from_attributes`) - [ ] 在 `app/services/` 中实现业务逻辑函数 - [ ] 在 `app/api/v1/` (或 `app/api/admin/`) 中定义路由(`APIRouter` + `Depends`) - [ ] 确认 `__init__.py` 中注册了新 router - [ ] 复杂操作记录 `log_operation()` - [ ] 写入操作后正确 `flush()` + `commit()` ### 新增后端 Model 时 ✅ - [ ] 在 `app/models/` 中新建或编辑 model 文件 - [ ] 继承 `Base` + 按需加 `TimestampMixin` / `SoftDeleteMixin` - [ ] 主键用 `generate_id()`,外键加 `index=True` - [ ] 在 `app/models/__init__.py` 中 import - [ ] 执行 `alembic revision --autogenerate -m "描述"` 生成迁移 - [ ] 执行 `alembic upgrade head` 应用迁移 ### 新增前端页面/功能时 ✅ - [ ] 在 `types/index.ts` 中定义/补充类型 - [ ] 在 `api/index.ts` 中添加 API 函数(含 mock 分支) - [ ] 在 `pages/` 中新建页面组件(`React.FC` + 默认导出) - [ ] 在 `App.tsx` 中注册路由 - [ ] 受保护路由包裹 `` - [ ] 在 `mock.ts` 中添加对应 mock 数据函数 --- ## 六、常见反模式(应避免) | ❌ 反模式 | ✅ 正确做法 | |----------|-----------| | Route 中写复杂 ORM 查询 | 抽到 `services/` 函数 | | Model 中有业务逻辑 | Model 只定义字段和关系 | | 前端直接用 `fetch` 调后端 API | 走 `api/index.ts` + `apiRequest` | | 新增 Model 不加到 `__init__.py` | 立即 import | | 主键用数据库自增 int | 用 `generate_id()` 生成 string | | Schema 不加 `from_attributes` | 响应 Schema 必须加 | | 前端用 `any` 不定义类型 | 在 `types/index.ts` 定义 | | 密码明文存储 | bcrypt 哈希(通过 `hash_password`) | --- ## 七、快速定位表 | 想修改什么 | 去哪里 | |-----------|--------| | 新增/改表字段 | `app/models/xxx.py` + `alembic revision` | | 新增接口 | `app/api/v1/xxx.py` + `app/schemas/xxx.py` + `app/services/xxx.py` | | 改业务规则 | `app/services/xxx.py` | | 改枚举值 | `app/enums/xxx.py` | | 加新页面 | `src/pages/XxxPage.tsx` + `App.tsx` 路由 + `src/types/index.ts` | | 加新 API 调用 | `src/api/index.ts` + `src/api/mock.ts` | | 改全局状态 | `src/store/useAuthStore.ts` / `useAppStore.ts` | | 改配置项 | `app/config.py` + `.env.example` | | 改中间件行为 | `app/middleware/xxx.py` | | 加异步任务 | `app/tasks/xxx.py` + `celery_app.py` 的 `task_routes` |