28 KiB
28 KiB
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:
# 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
# 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 约定
# 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 datetimePaginatedResponse— 分页响应基类
2.5 API 路由约定
# 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 依赖注入约定
# 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 服务层约定
# 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 枚举约定
# 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 异常处理约定
# app/utils/exceptions.py — 预定义异常
InsufficientCreditsError # 402 积分不足
CaptchaFailedError # 400 验证码失败
RecordNotFoundError # 404 记录不存在
ProjectNotFoundError # 404 项目不存在
InvalidStatusError # 400 状态不允许操作
# 在 service 或 route 中抛出
raise InsufficientCreditsError()
2.10 认证约定
# 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 校验
鉴权流程:
- 前端 Authorization header:
Bearer <token> HTTPBearer提取 token →decode_access_token获取user_id- 查 User 表验证
is_active - 检查
user_must_set_password(短信注册用户首次需设置密码)
2.11 数据库 Session 模式
# 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):
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: trueheader → 解密请求体 - GET 请求无 body 但响应仍加密
- 支付回调路径白名单跳过加密 (
/payments/alipay/callback,/payments/wechat/callback)
2.13 Celery 任务约定
# 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 配置约定
# 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 操作日志约定
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<T>(加密/解密、错误处理)
│ ├── 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 调用约定(核心)
// ── client.ts — 核心请求函数 ──
// 所有请求统一走 apiRequest<T>,自动处理加密和响应解析
async function apiRequest<T>(path: string, options: RequestOptions): Promise<T>
// 调用参数:
interface RequestOptions {
method?: string // GET/POST/PUT/DELETE,默认 GET
body?: unknown // 请求体(自动 JSON 序列化 + 加密)
auth?: boolean // 是否带 Authorization header,默认 true
encryptBody?: boolean // 是否加密,默认跟随全局开关
signal?: AbortSignal // 取消请求
skipAuthRedirect?: boolean // 401 时不跳转登录页
}
// ── index.ts — 业务 API 函数 ──
// 命名约定:动词 + 业务名,camelCase
export async function login(username: string, password: string): Promise<User> {
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<Project[]> {
if (USE_MOCK) return mock.mockGetProjects();
return api.get<Project[]>('/projects');
}
// POST 创建
export async function createProject(name: string, industry: Industry): Promise<Project> {
if (USE_MOCK) return mock.mockCreateProject(name, industry);
return api.post<Project>('/projects', { name, industry });
}
// 带查询参数的 GET
export async function getRecordsPage(params): Promise<GenerationRecordPageListOut> {
const query = new URLSearchParams();
if (params.projectId) query.set('project_id', params.projectId);
query.set('page', String(page));
return api.get<GenerationRecordPageListOut>(`/generation-records?${query.toString()}`);
}
关键约定:
api.get/post/put/delete<T>(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)
// 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<void>;
logout: () => Promise<void>;
checkAuth: () => Promise<void>; // 启动时验证 token 有效性
}
export const useAuthStore = create<AuthState>((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 页面组件约定
// 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<Project[]>([]);
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 (
<div>
<Typography.Title level={4}>我的项目</Typography.Title>
{/* ... */}
</div>
);
};
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 路由约定
// App.tsx — 路由结构
// 公开路由
<Route path="/login" element={<LoginPage />} />
<Route path="/join-team" element={<JoinTeamPage />} />
// 受保护路由(需要登录)
<Route path="/" element={<ProtectedRoute><AppLayout /></ProtectedRoute>}>
<Route index element={<Navigate to="/projects" replace />} />
<Route path="projects" element={<ProjectsPage />} />
<Route path="projects/:projectId/generate" element={<GeneratePage />} />
<Route path="*" element={<Navigate to="/projects" replace />} />
</Route>
约定:
- 路由定义集中在
App.tsx - 登录保护通过
ProtectedRoute包装器实现(检查useAuthStore.user) - 未登录用户访问受保护路由 → 跳
/login - 动态路由参数用
:paramName - 嵌套路由用
<Outlet />渲染子页面
3.7 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 加密约定
// 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 时,前端走本地假数据,无需后端。
// api/index.ts 中每个 API 函数都检查 USE_MOCK
const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true';
export async function getProjects(): Promise<Project[]> {
if (USE_MOCK) return mock.mockGetProjects(); // ← 先看 mock
return api.get<Project[]>('/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 分页约定
后端返回:
@router.get("", response_model=GenerationRecordPageListOut)
# { items: [...], total: 100, page: 1, page_size: 20 }
前端类型:
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 无效/过期 → 自动跳/login403— 权限不足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中注册路由 - 受保护路由包裹
<ProtectedRoute> - 在
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 |