当前位置: 首页 > news >正文

使用FastAPI和React以及MongoDB构建全栈Web应用07 FastAPI实现经典三层架构

一、单文件简单实现

1.1 开发用户增删改查接口

main.py

from fastapi import FastAPI, Request, Query, HTTPException
from fastapi.responses import JSONResponse
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel
from bson import ObjectId, errors as bson_errors
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional, List# 创建FastAPI应用实例
app = FastAPI(title="用户管理API",description="提供用户的增删改查和分页功能",version="1.0.0"
)# MongoDB 连接配置
MONGODB_URL = "mongodb://zhangdapeng:zhangdapeng520@localhost:27017"
DATABASE_NAME = "blogdb"# 初始化MongoDB连接
client = AsyncIOMotorClient(MONGODB_URL)
db = client[DATABASE_NAME]
user_collection = db["users"]# 配置CORS中间件
app.add_middleware(CORSMiddleware,allow_origins=["*"],  # 在生产环境中应该指定具体的域名allow_credentials=True,allow_methods=["*"],allow_headers=["*"],
)# 全局异常处理器
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):"""全局异常处理器,捕获所有未处理的异常并返回统一的错误响应"""error_msg = str(exc)if isinstance(exc, bson_errors.InvalidId):return JSONResponse(status_code=400,content={"detail": "无效的ID格式"})return JSONResponse(status_code=500,content={"detail": f"服务器内部错误: {error_msg}"})# 用户数据模型
class User(BaseModel):"""用户数据模型,定义用户的基本信息字段"""name: strage: intgender: strclass Config:json_schema_extra = {"example": {"name": "张三","age": 25,"gender": "男"}}# 用户分页响应模型
class UserResponse(BaseModel):"""用户列表分页响应模型"""total: intusers: List[dict]# API接口实现
@app.post("/users/", response_model=dict, summary="创建新用户")
async def create_user(user: User):"""创建新用户Args:user: 用户信息对象Returns:dict: 包含用户ID和信息的字典"""result = await user_collection.insert_one(user.dict())return {"_id": str(result.inserted_id), **user.dict()}@app.get("/users/", response_model=UserResponse, summary="获取用户列表")
async def get_users(page: int = Query(1, gt=0, description="页码"),page_size: int = Query(10, gt=0, le=100, description="每页数量")
):"""获取用户列表,支持分页查询Args:page: 当前页码page_size: 每页显示的数量Returns:UserResponse: 包含总数和用户列表的响应对象"""skip = (page - 1) * page_sizetotal = await user_collection.count_documents({})users = await user_collection.find().skip(skip).limit(page_size).to_list(length=page_size)# 转换ObjectId为字符串for user in users:user["_id"] = str(user["_id"])return {"total": total, "users": users}@app.put("/users/{user_id}", summary="更新用户信息")
async def update_user(user_id: str, user: User):"""更新指定用户的信息Args:user_id: 用户IDuser: 更新的用户信息Returns:dict: 更新结果信息"""result = await user_collection.update_one({"_id": ObjectId(user_id)},{"$set": user.dict()})if result.modified_count == 0:raise HTTPException(status_code=404, detail="用户不存在")return {"message": "用户更新成功"}@app.delete("/users/{user_id}", summary="删除用户")
async def delete_user(user_id: str):"""删除指定的用户Args:user_id: 要删除的用户IDReturns:dict: 删除结果信息"""result = await user_collection.delete_one({"_id": ObjectId(user_id)})if result.deleted_count == 0:raise HTTPException(status_code=404, detail="用户不存在")return {"message": "用户删除成功"}@app.get("/users/{user_id}", summary="获取单个用户信息")
async def get_user(user_id: str):"""获取指定用户的详细信息Args:user_id: 用户IDReturns:dict: 用户信息"""user = await user_collection.find_one({"_id": ObjectId(user_id)})if user is None:raise HTTPException(status_code=404, detail="用户不存在")user["_id"] = str(user["_id"])return user# 应用启动入口
if __name__ == '__main__':import uvicornuvicorn.run(app, host='0.0.0.0', port=8080)

1.2 创建React项目

package.json

{"name": "user_manager","private": true,"version": "0.0.0","type": "module","scripts": {"dev": "vite","build": "vite build","lint": "eslint .","preview": "vite preview"},"dependencies": {"react": "^18.3.1","react-dom": "^18.3.1","react-router-dom": "^7.6.0"},"devDependencies": {"@types/react": "^18.3.21","@types/react-dom": "^18.3.7","@vitejs/plugin-react": "^4.4.1","globals": "^15.15.0","vite": "^6.3.5"}
}

vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'export default defineConfig({plugins: [react()],
})

index.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><link rel="icon" type="image/svg+xml" href="/vite.svg" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>FastAPI+React用户管理系统</title></head><body><div id="root"></div><script type="module" src="/src/main.jsx"></script></body>
</html>

src/main.jsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'createRoot(document.getElementById('root')).render(<StrictMode><App /></StrictMode>,
)

src/App.jsx

import {Route, BrowserRouter, Routes} from "react-router-dom";
import UserList from "./page/UserList.jsx";function App() {return (<BrowserRouter><Routes><Route path="/" element={<UserList/>}/></Routes></BrowserRouter>)
}export default App;

src/page/UserList.jsx

function UserList() {return (<div><h1>用户列表</h1><p>这里是用户列表的内容。</p></div>)
}export default UserList;

运行:

pnpm i
pnpm dev

1.3 引入zdpreact.css

src/zdpreact.css

/* 表格基础样式 */
.table {width: 100%;                  /* 表格宽度占满容器 */border-collapse: collapse;    /* 合并表格边框 */margin-bottom: 20px;         /* 底部外边距 */
}/* 表格头部单元格样式 */
.table th {background-color: #f2f2f2;   /* 表头背景色 */padding: 12px;               /* 内边距 */border: 1px solid #ddd;      /* 边框样式 */text-align: left;            /* 文字左对齐 */
}/* 表格数据单元格样式 */
.table td {padding: 12px;               /* 内边距 */border: 1px solid #ddd;      /* 边框样式 */
}/* 分页容器样式 */
.pagination {display: flex;               /* 弹性布局 */justify-content: center;     /* 水平居中 */align-items: center;         /* 垂直居中 */gap: 10px;                  /* 按钮之间的间距 */margin-top: 20px;           /* 顶部外边距 */
}/* 分页按钮基础样式 */
.pagination-button {padding: 8px 12px;          /* 内边距 */border: 1px solid #ddd;     /* 边框样式 */background-color: white;    /* 背景色 */cursor: pointer;            /* 鼠标指针样式 */border-radius: 4px;         /* 圆角 */transition: all 0.3s;       /* 过渡效果 */
}/* 分页按钮悬停效果 */
.pagination-button:hover:not(:disabled) {background-color: #f0f0f0;  /* 悬停背景色 */border-color: #999;         /* 悬停边框色 */
}/* 当前页码按钮样式 */
.pagination-button.active {background-color: #1890ff; /* 激活状态背景色 */color: white;              /* 激活状态文字颜色 */border-color: #1890ff;     /* 激活状态边框色 */
}/* 禁用按钮样式 */
.pagination-button:disabled {background-color: #f5f5f5;  /* 禁用状态背景色 */color: #d9d9d9;            /* 禁用状态文字颜色 */cursor: not-allowed;       /* 禁用状态鼠标样式 */border-color: #d9d9d9;     /* 禁用状态边框色 */
}/* 表格操作按钮样式 */
.table-button {padding: 4px 8px;          /* 内边距 */border: 1px solid #ddd;    /* 边框样式 */background-color: white;   /* 背景色 */cursor: pointer;           /* 鼠标指针样式 */border-radius: 4px;        /* 圆角 */transition: all 0.3s;      /* 过渡效果 */
}.table-button:hover {background-color: #f0f0f0; /* 悬停背景色 */border-color: #999;        /* 悬停边框色 */
}/* 删除按钮特殊样式 */
.table-button.delete {color: #ff4d4f;           /* 文字颜色 */border-color: #ff4d4f;    /* 边框颜色 */
}.table-button.delete:hover {background-color: #fff1f0; /* 悬停背景色 */border-color: #ff7875;     /* 悬停边框色 */
}/* 分页选择器样式 */
.pagination-select {padding: 8px 12px;          /* 内边距 */border: 1px solid #ddd;     /* 边框样式 */border-radius: 4px;         /* 圆角 */background-color: white;    /* 背景色 */cursor: pointer;            /* 鼠标指针样式 */margin-left: 10px;         /* 左边距 */outline: none;             /* 移除默认轮廓 */
}.pagination-select:hover {border-color: #999;        /* 悬停边框色 */
}.pagination-select:focus {border-color: #1890ff;     /* 聚焦边框色 */
}/* 表格工具栏样式 */
.table-toolbar {margin-bottom: 16px;display: flex;justify-content: flex-end;
}/* 主要按钮样式 */
.table-button.primary {background-color: #1890ff;color: white;border-color: #1890ff;
}.table-button.primary:hover {background-color: #40a9ff;border-color: #40a9ff;
}/* 表单样式 */
.form {width: 100%;max-width: 400px;padding: 20px;background: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}.form-item {margin-bottom: 16px;
}.form-item label {display: block;margin-bottom: 8px;
}/* 表单项输入框和选择框通用样式 */
.form-item input,
.form-item select {width: 100%;padding: 8px 12px;border: 1px solid #d9d9d9;border-radius: 4px;font-size: 14px;line-height: 1.5;transition: all 0.3s;box-sizing: border-box;background-color: #fff;
}/* 输入框和选择框悬停样式 */
.form-item input:hover,
.form-item select:hover {border-color: #40a9ff;
}/* 输入框和选择框聚焦样式 */
.form-item input:focus,
.form-item select:focus {border-color: #40a9ff;outline: none;box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}/* 选择框特殊样式 */
.form-item select {appearance: none;background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8.5L1.5 4h9z'/%3E%3C/svg%3E");background-repeat: no-repeat;background-position: right 8px center;padding-right: 30px;
}/* 表单项标签样式 */
.form-item label {display: block;margin-bottom: 8px;color: #000000d9;font-size: 14px;
}.form-buttons {display: flex;gap: 8px;justify-content: flex-end;margin-top: 20px;
}/* 模态框遮罩层 */
.modal-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, 0.5);display: flex;justify-content: center;align-items: center;z-index: 1000;
}/* 模态框容器 */
.modal {background: white;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);min-width: 400px;
}/* 模态框头部 */
.modal-header {padding: 16px 24px;border-bottom: 1px solid #f0f0f0;
}.modal-header h3 {margin: 0;color: #000000d9;font-weight: 500;font-size: 16px;line-height: 1.4;
}/* 模态框内容区 */
.modal-content {padding: 24px;
}/* 模态框中的表单样式 */
.modal-form {width: auto;max-width: none;padding: 0;background: none;box-shadow: none;
}/* 模态框按钮容器 */
.modal-buttons {display: flex;justify-content: flex-end;gap: 8px;margin-top: 24px;padding-top: 16px;border-top: 1px solid #f0f0f0;
}/* 表格容器样式 */
.table-container {width: 100%;overflow-x: auto;border-radius: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}/* 空状态样式 */
.table-empty-state,
.table-loading-state {padding: 40px 0;text-align: center;color: #666;
}.empty-icon {font-size: 48px;margin-bottom: 16px;
}.empty-text,
.loading-text {font-size: 14px;margin: 0;color: #666;
}/* 加载状态样式 */
.loading-spinner {display: inline-block;width: 30px;height: 30px;border: 3px solid #f3f3f3;border-top: 3px solid #3498db;border-radius: 50%;animation: spin 1s linear infinite;margin-bottom: 16px;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}/* 表格悬停效果 */
.table tr:hover {background-color: #f5f5f5;
}/* 确认删除弹窗样式 */
.confirm-modal-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, 0.65);backdrop-filter: blur(4px);display: flex;justify-content: center;align-items: center;z-index: 1000;animation: confirmModalFadeIn 0.3s ease;
}.confirm-modal {background: white;border-radius: 12px;box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);min-width: 400px;padding: 24px;transform-origin: center;animation: confirmModalScaleIn 0.3s ease;
}.confirm-modal-title {margin: 0 0 16px 0;color: #1f2937;font-size: 20px;font-weight: 600;line-height: 1.4;
}.confirm-modal-content {margin: 0 0 24px 0;color: #4b5563;font-size: 16px;line-height: 1.6;
}.confirm-modal-buttons {display: flex;justify-content: flex-end;gap: 12px;margin-top: 24px;
}.confirm-modal-button {padding: 8px 20px;border: none;border-radius: 6px;font-weight: 500;cursor: pointer;transition: all 0.2s ease;
}.confirm-modal-button-delete {color: white;background-color: #dc2626;
}.confirm-modal-button-delete:hover {background-color: #b91c1c;transform: translateY(-1px);
}.confirm-modal-button-cancel {color: #4b5563;background-color: #f3f4f6;
}.confirm-modal-button-cancel:hover {background-color: #e5e7eb;transform: translateY(-1px);
}@keyframes confirmModalFadeIn {from {opacity: 0;}to {opacity: 1;}
}@keyframes confirmModalScaleIn {from {opacity: 0;transform: scale(0.95);}to {opacity: 1;transform: scale(1);}
}

修改 src/App.jsx

import {Route, BrowserRouter, Routes} from "react-router-dom";
import "./zdpreact.css"
import UserList from "./page/UserList.jsx";function App() {return (<BrowserRouter><Routes><Route path="/" element={<UserList/>}/></Routes></BrowserRouter>)
}export default App;

1.4 引入zdpreq.js

src/api/zdpreq.js

/*** HTTP请求客户端类* 封装了基本的HTTP请求方法,提供统一的接口和错误处理*/
class ZDPReq {/*** 构造函数* @param {Object} config - 配置对象* @param {string} config.baseURL - API基础URL,默认为http://localhost:8080* @param {number} config.timeout - 请求超时时间(毫秒),默认为5000ms*/constructor(config = {}) {this.config = {baseURL: 'http://localhost:8080',timeout: 5000,...config};}/*** 创建超时Promise* 用于实现请求超时控制,当请求超过指定时间后自动reject* @param {number} timeout - 超时时间(毫秒)* @returns {Promise} 超时Promise* @private*/_createTimeoutPromise(timeout) {return new Promise((_, reject) => {setTimeout(() => {reject(new Error('请求超时'));}, timeout);});}/*** 处理HTTP响应* 统一处理响应状态和错误,将响应转换为JSON格式* @param {Response} response - fetch API的响应对象* @returns {Promise<Object>} 解析后的JSON数据* @throws {Error} 当响应状态不为2xx时抛出错误* @private*/async _processResponse(response) {if (!response.ok) {const error = await response.json().catch(() => ({}));throw new Error(error.detail || `请求失败: ${response.status}`);}return response.json();}/*** 构建完整的请求URL* 将baseURL、路径和查询参数组合成完整的URL* @param {string} url - 请求路径* @param {Object} params - URL查询参数对象* @returns {string} 完整的URL字符串* @private*/_createFullURL(url, params) {const fullURL = new URL(this.config.baseURL + url);if (params) {Object.entries(params).forEach(([key, value]) => {fullURL.searchParams.append(key, value);});}return fullURL.toString();}/*** 发送HTTP请求的核心方法* 处理请求配置、超时控制和错误处理* @param {string} method - HTTP方法(GET、POST、PUT、DELETE等)* @param {string} url - 请求路径* @param {Object} options - 请求配置选项* @param {Object} [options.data] - 请求体数据* @param {Object} [options.params] - URL查询参数* @returns {Promise<Object>} 响应数据* @throws {Error} 请求失败时抛出错误* @private*/async _request(method, url, options = {}) {const { data, params } = options;const fullURL = this._createFullURL(url, params);try {const fetchOptions = {method,headers: {'Content-Type': 'application/json'}};if (data) {fetchOptions.body = JSON.stringify(data);}const response = await Promise.race([fetch(fullURL, fetchOptions),this._createTimeoutPromise(this.config.timeout)]);return this._processResponse(response);} catch (error) {throw new Error(error.message || '请求失败');}}/*** 发送GET请求* @param {string} url - 请求路径* @param {Object} [params] - URL查询参数* @returns {Promise<Object>} 响应数据*/async get(url, params) {return this._request('GET', url, { params });}/*** 发送POST请求* @param {string} url - 请求路径* @param {Object} data - 请求体数据* @returns {Promise<Object>} 响应数据*/async post(url, data) {return this._request('POST', url, { data });}/*** 发送PUT请求* @param {string} url - 请求路径* @param {Object} data - 请求体数据* @returns {Promise<Object>} 响应数据*/async put(url, data) {return this._request('PUT', url, { data });}/*** 发送DELETE请求* @param {string} url - 请求路径* @returns {Promise<Object>} 响应数据*/async delete(url) {return this._request('DELETE', url);}
}// 创建并导出HTTP客户端实例
export const zdpreq = new ZDPReq();

1.5 封装用户API接口请求

src/api/userApi.js

import { zdpreq } from './zdpreq';// 用户API对象
export const userApi = {// 获取用户列表getPageUser: async (page = 1, pageSize = 10) => {try {return await zdpreq.get('/users/', {page,page_size: pageSize});} catch (error) {throw new Error(error.message || '获取用户列表失败');}},// 获取单个用户信息getUser: async (userId) => {try {return await zdpreq.get(`/users/${userId}`);} catch (error) {throw new Error(error.message || '获取用户信息失败');}},// 创建新用户addUser: async (userData) => {try {return await zdpreq.post('/users/', userData);} catch (error) {throw new Error(error.message || '创建用户失败');}},// 更新用户信息updateUser: async (userId, userData) => {try {return await zdpreq.put(`/users/${userId}`, userData);} catch (error) {throw new Error(error.message || '更新用户失败');}},// 删除用户deleteUser: async (userId) => {try {return await zdpreq.delete(`/users/${userId}`);} catch (error) {throw new Error(error.message || '删除用户失败');}}
};

1.6 引入表格组件

src/zdpreact/component/Table.jsx

import React from 'react';/*** 通用表格组件* @zdpreact* @description 这是一个可复用的表格组件,支持自定义列配置和数据渲染* * @param {Object[]} columns - 表格列配置数组* @param {string} columns[].key - 列的唯一标识,用于数据映射* @param {string} columns[].title - 列的标题文本* @param {function} [columns[].render] - 可选的自定义渲染函数,用于自定义单元格内容* * @param {Object[]} data - 表格数据数组,每个对象代表一行数据* @param {boolean} loading - 加载状态标志,控制是否显示加载动画*/
function Table({ columns, data, loading }) {/*** 渲染空数据状态的UI组件* @returns {JSX.Element} 返回空状态的JSX元素*/const renderEmpty = () => (<div className="table-empty-state"><div className="empty-icon">📭</div><p className="empty-text">暂无数据</p></div>);/*** 渲染加载状态的UI组件* @returns {JSX.Element} 返回加载状态的JSX元素*/const renderLoading = () => (<div className="table-loading-state"><div className="loading-spinner"></div><p className="loading-text">加载中...</p></div>);return (<div className="table-container"><table className="table">{/* 表头部分 */}<thead><tr>{columns.map(column => (<th key={column.key}>{column.title}</th>))}</tr></thead>{/* 表格主体部分 */}<tbody>{loading ? (// 加载状态显示加载动画<tr><td colSpan={columns.length}>{renderLoading()}</td></tr>) : data.length > 0 ? (// 有数据时渲染数据行data.map((row, rowIndex) => (<tr key={row.id || rowIndex}>{columns.map(column => (<td key={column.key}>{/* 如果列配置中有render函数则使用自定义渲染,否则直接显示数据 */}{column.render ? column.render(row) : row[column.key]}</td>))}</tr>))) : (// 无数据时显示空状态<tr><td colSpan={columns.length}>{renderEmpty()}</td></tr>)}</tbody></table></div>);
}export default Table;

1.7 引入分页组件

src/zdpreact/component/Pagination.jsx

import React from 'react';// 分页组件
function Pagination({ currentPage, totalPages, onPageChange, pageSize, onPageSizeChange }) {// 定义可选的每页显示数量const pageSizeOptions = [2, 5, 10, 20, 50, 100];return (<div className="pagination">{/* 上一页按钮 */}<button className="pagination-button"onClick={() => onPageChange(currentPage - 1)}disabled={currentPage === 1}>上一页</button>{/* 页码按钮 */}{Array.from({ length: totalPages }, (_, index) => (<buttonkey={index + 1}className={`pagination-button ${currentPage === index + 1 ? 'active' : ''}`}onClick={() => onPageChange(index + 1)}>{index + 1}</button>))}{/* 下一页按钮 */}<button className="pagination-button"onClick={() => onPageChange(currentPage + 1)}disabled={currentPage === totalPages}>下一页</button>{/* 每页显示数量选择器 */}<select className="pagination-select"value={pageSize}onChange={(e) => onPageSizeChange(Number(e.target.value))}>{pageSizeOptions.map(size => (<option key={size} value={size}>{size} 条/页</option>))}</select></div>);
}export default Pagination;

1.8 引入表单模态框组件

src/zdpreact/component/FormModal.jsx

import React from 'react';/*** 表单模态框组件* @param {Object} props 组件属性* @param {boolean} props.visible 是否显示模态框* @param {string} props.title 模态框标题* @param {Array} props.fields 表单字段配置* @param {Object} props.initialValues 表单初始值* @param {function} props.onSubmit 表单提交回调* @param {function} props.onCancel 取消按钮回调*/
function FormModal({ visible, title, fields, initialValues = {}, onSubmit, onCancel }) {if (!visible) return null;const handleSubmit = (e) => {e.preventDefault();const formData = new FormData(e.target);const data = {};fields.forEach(field => {const value = formData.get(field.name);data[field.name] = field.type === 'number' ? parseInt(value) : value;});onSubmit(data);};return (<div className="modal-overlay"><div className="modal"><div className="modal-header"><h3>{title}</h3></div><div className="modal-content"><form className="form modal-form" onSubmit={handleSubmit}>{fields.map(field => (<div className="form-item" key={field.name}><label>{field.label}:</label>{field.type === 'select' ? (<select name={field.name} required={field.required}defaultValue={initialValues[field.name] || ''}><option value="">{field.placeholder || '请选择'}</option>{field.options.map(option => (<option key={option.value} value={option.value}>{option.label}</option>))}</select>) : (<inputtype={field.type}name={field.name}required={field.required}placeholder={field.placeholder}defaultValue={initialValues[field.name] || ''}/>)}</div>))}<div className="modal-buttons"><button type="submit" className="table-button primary">确认</button><button type="button" className="table-button"onClick={onCancel}>取消</button></div></form></div></div></div>);
}export default FormModal;

1.9 引入确认模态框组件

src/zdpreact/component/ConfirmModal.jsx

import React from 'react';/*** 确认模态框组件* @param {Object} props 组件属性* @param {boolean} props.visible 是否显示模态框* @param {string} props.title 模态框标题* @param {string} props.content 模态框内容* @param {function} props.onConfirm 确认按钮回调* @param {function} props.onCancel 取消按钮回调*/
function ConfirmModal({ visible, title, content, onConfirm, onCancel }) {if (!visible) return null;return (<div className="confirm-modal-overlay"><div className="confirm-modal"><h3 className="confirm-modal-title">{title}</h3><p className="confirm-modal-content">{content}</p><div className="confirm-modal-buttons"><button className="confirm-modal-button confirm-modal-button-delete"onClick={onConfirm}>确认</button><button className="confirm-modal-button confirm-modal-button-cancel"onClick={onCancel}>取消</button></div></div></div>);
}export default ConfirmModal;

1.10 实现用户增删改查功能

src/page/UserList.jsx

import { useState, useEffect } from 'react';
import Pagination from '../zdpreact/component/Pagination.jsx';
import Table from '../zdpreact/component/Table.jsx';
import ConfirmModal from '../zdpreact/component/ConfirmModal.jsx';
import FormModal from '../zdpreact/component/FormModal.jsx';
import { userApi } from '../api/userApi.js';/*** 用户列表组件* 实现用户的增删改查功能,包含分页、搜索、表单验证等功能*/
function UserList() {// ================ 状态管理 ================// 用户数据相关状态const [users, setUsers] = useState([]); // 用户列表数据const [loading, setLoading] = useState(false); // 加载状态const [error, setError] = useState(null); // 错误信息// 分页相关状态const [currentPage, setCurrentPage] = useState(1); // 当前页码const [itemsPerPage, setItemsPerPage] = useState(10); // 每页条数const [totalItems, setTotalItems] = useState(0); // 总记录数// 模态框状态管理const [deleteConfirm, setDeleteConfirm] = useState({visible: false,record: null});const [addUserModal, setAddUserModal] = useState({visible: false});const [editUserModal, setEditUserModal] = useState({visible: false,record: null});// ================ 数据加载 ================/*** 加载用户列表数据* 包含错误处理和加载状态管理*/const loadUsers = async () => {try {setLoading(true);setError(null);const response = await userApi.getPageUser(currentPage, itemsPerPage);setUsers(response.users);setTotalItems(response.total);} catch (err) {setError('加载用户数据失败:' + (err.message || '未知错误'));console.error('加载用户数据失败:', err);} finally {setLoading(false);}};// 监听分页参数变化,重新加载数据useEffect(() => {loadUsers();}, [currentPage, itemsPerPage]);// ================ 事件处理 ================/*** 处理每页显示数量变化* @param {number} newSize - 新的每页显示数量*/const handlePageSizeChange = (newSize) => {setItemsPerPage(newSize);setCurrentPage(1); // 切换每页数量时重置为第一页};/*** 处理新增用户按钮点击*/const showAddUserModal = () => {setAddUserModal({ visible: true });};/*** 处理编辑按钮点击* @param {Object} record - 当前行用户数据*/const handleEdit = (record) => {setEditUserModal({visible: true,record: { ...record } // 创建数据副本,避免直接修改原数据});};/*** 显示删除确认框* @param {Object} record - 当前行用户数据*/const showDeleteConfirm = (record) => {setDeleteConfirm({visible: true,record});};// ================ 模态框操作处理 ================/*** 处理删除确认*/const handleDeleteConfirm = async () => {try {setLoading(true);await userApi.deleteUser(deleteConfirm.record._id);await loadUsers();setDeleteConfirm({visible: false,record: null});} catch (err) {setError('删除用户失败:' + (err.message || '未知错误'));console.error('删除用户失败:', err);} finally {setLoading(false);}};/*** 处理删除取消*/const handleDeleteCancel = () => {setDeleteConfirm({visible: false,record: null});};/*** 处理新增用户确认* @param {Object} formData - 表单数据*/const handleAddUserConfirm = async (formData) => {try {setLoading(true);await userApi.addUser(formData);await loadUsers();setAddUserModal({ visible: false });} catch (err) {setError('新增用户失败:' + (err.message || '未知错误'));console.error('新增用户失败:', err);} finally {setLoading(false);}};/*** 处理新增用户取消*/const handleAddUserCancel = () => {setAddUserModal({ visible: false });};/*** 处理编辑用户确认* @param {Object} formData - 表单数据*/const handleEditUserConfirm = async (formData) => {try {setLoading(true);await userApi.updateUser(editUserModal.record._id, formData);await loadUsers();setEditUserModal({visible: false,record: null});} catch (err) {setError('更新用户失败:' + (err.message || '未知错误'));console.error('更新用户失败:', err);} finally {setLoading(false);}};/*** 处理编辑用户取消*/const handleEditUserCancel = () => {setEditUserModal({visible: false,record: null});};// ================ 配置项 ================// 表格列定义const columns = [{ key: 'name', title: '姓名' },{ key: 'age', title: '年龄' },{ key: 'gender', title: '性别' },{key: 'action',title: '操作',render: (record) => (<div style={{ display: 'flex', gap: '8px' }}><buttonclassName="table-button"onClick={() => handleEdit(record)}disabled={loading}>编辑</button><buttonclassName="table-button delete"onClick={() => showDeleteConfirm(record)}disabled={loading}>删除</button></div>)}];// 用户表单字段配置const userFields = [{name: 'name',label: '姓名',type: 'text',required: true},{name: 'age',label: '年龄',type: 'number',required: true},{name: 'gender',label: '性别',type: 'select',required: true,options: [{ value: '男', label: '男' },{ value: '女', label: '女' }]}];// ================ 渲染 ================return (<div className="App"><header className="App-header"><h1>用户列表</h1>{/* 错误信息显示 */}{error && (<div className="error-message" style={{ color: 'red', margin: '10px 0' }}>{error}</div>)}{/* 操作栏 */}<div className="table-toolbar"><buttonclassName="table-button primary"onClick={showAddUserModal}disabled={loading}>新增用户</button></div>{/* 用户列表表格 */}<Tablecolumns={columns}data={users}loading={loading}/>{/* 分页组件 */}<PaginationcurrentPage={currentPage}totalPages={Math.ceil(totalItems / itemsPerPage)}onPageChange={setCurrentPage}pageSize={itemsPerPage}onPageSizeChange={handlePageSizeChange}/>{/* 删除确认框 */}<ConfirmModalvisible={deleteConfirm.visible}title="确认删除"content={`确定要删除用户 "${deleteConfirm.record?.name}" 吗?`}onConfirm={handleDeleteConfirm}onCancel={handleDeleteCancel}/>{/* 新增用户表单 */}<FormModalvisible={addUserModal.visible}title="新增用户"fields={userFields}onSubmit={handleAddUserConfirm}onCancel={handleAddUserCancel}/>{/* 编辑用户表单 */}<FormModalvisible={editUserModal.visible}title="编辑用户"fields={userFields}initialValues={editUserModal.record}onSubmit={handleEditUserConfirm}onCancel={handleEditUserCancel}/></header></div>);
}export default UserList;

二、经典三层架构

2.1 MongoDB数据库依赖

app/dependency/database.py

from typing import AsyncGenerator
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
from fastapi import Depends# MongoDB配置
MONGO_URL = "mongodb://zhangdapeng:zhangdapeng520@localhost:27017/"
DATABASE_NAME = "users_db"# 创建MongoDB客户端
client = AsyncIOMotorClient(MONGO_URL)async def get_database() -> AsyncGenerator[AsyncIOMotorDatabase, None]:"""获取数据库连接的依赖函数"""try:db = client[DATABASE_NAME]yield dbfinally:# 在这里可以添加清理代码如果需要pass# 获取用户集合的依赖函数
async def get_user_collection():"""获取用户集合的依赖函数"""db = client[DATABASE_NAME]return db["users"]

2.2 跨域中间件

app/middleware/cors_middleware.py

from fastapi.middleware.cors import CORSMiddlewaredef setup_cors_middleware(app):"""配置CORS中间件"""app.add_middleware(CORSMiddleware,allow_origins=["*"],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],

2.3 全局异常处理

app/middleware/error_handler.py

from fastapi import Request
from fastapi.responses import JSONResponse
from bson import errors as bson_errorsdef setup_error_handler(app):"""配置全局异常处理器"""@app.exception_handler(Exception)async def global_exception_handler(request: Request, exc: Exception):error_msg = str(exc)if isinstance(exc, bson_errors.InvalidId):return JSONResponse(status_code=400,content={"message": "Invalid ObjectId"},)return JSONResponse(status_code=500,content={"message": f"服务器内部错误: {error_msg}"},)

2.4 初始化所有中间件

app/middleware/__init__.py

from .cors_middleware import setup_cors_middleware
from .error_handler import setup_error_handlerdef setup_middlewares(app):"""初始化所有中间件"""setup_cors_middleware(app)setup_error_handler(app)

2.5 用户请求参数

app/schema/user_model.py

from pydantic import BaseModel
from typing import Listclass User(BaseModel):name: strage: intgender: strclass Config:json_schema_extra = {"example": {"name": "张三","age": 30,"gender": "男"}}class UserResponse(BaseModel):total: intusers: List[dict]

2.6 用户dao层

app/dao/user_dao.py

from bson import ObjectId
from ..dependency.database import get_user_collectionclass UserDAO:def __init__(self):self.collection = Noneasync def initialize(self):"""初始化数据库集合"""self.collection = await get_user_collection()async def create_user(self, user_dict: dict) -> str:"""创建用户"""await self.initialize()result = await self.collection.insert_one(user_dict)return str(result.inserted_id)async def get_users(self, skip: int, limit: int) -> tuple[list, int]:"""获取用户列表"""await self.initialize()users = await self.collection.find().skip(skip).limit(limit).to_list(length=limit)total = await self.collection.count_documents({})for user in users:user["_id"] = str(user["_id"])return users, totalasync def update_user(self, user_id: str, user_dict: dict) -> bool:"""更新用户"""await self.initialize()result = await self.collection.update_one({"_id": ObjectId(user_id)},{"$set": user_dict})return result.modified_count > 0async def delete_user(self, user_id: str) -> bool:"""删除用户"""await self.initialize()result = await self.collection.delete_one({"_id": ObjectId(user_id)})return result.deleted_count > 0async def get_user(self, user_id: str) -> dict:"""获取单个用户"""await self.initialize()user = await self.collection.find_one({"_id": ObjectId(user_id)})if user:user["_id"] = str(user["_id"])return user

2.7 用户service层

app/service/user_service.py

from fastapi import HTTPException
from ..dao.user_dao import UserDAO
from ..schema.user_model import Userclass UserService:def __init__(self):self.user_dao = UserDAO()async def create_user(self, user: User) -> dict:"""创建用户服务"""user_dict = user.dict()user_id = await self.user_dao.create_user(user_dict)return {"message": "用户创建成功", "id": user_id}async def get_users(self, skip: int, page_size: int) -> dict:"""获取用户列表服务"""users, total = await self.user_dao.get_users(skip, page_size)return {"total": total, "users": users}async def update_user(self, user_id: str, user: User) -> dict:"""更新用户服务"""user_dict = user.dict()if not await self.user_dao.update_user(user_id, user_dict):raise HTTPException(status_code=404, detail="用户不存在")return {"message": "用户更新成功"}async def delete_user(self, user_id: str) -> dict:"""删除用户服务"""if not await self.user_dao.delete_user(user_id):raise HTTPException(status_code=404, detail="用户不存在")return {"message": "用户删除成功"}async def get_user(self, user_id: str) -> User:"""获取单个用户服务"""user = await self.user_dao.get_user(user_id)if user is None:raise HTTPException(status_code=404, detail="用户不存在")return User(**user)

2.8 用户controller层

app/controller/user_controller.py

from fastapi import APIRouter, Query
from ..schema.user_model import User, UserResponse
from ..service.user_service import UserServicerouter = APIRouter(tags=["用户管理"])
user_service = UserService()@router.post("/users/", response_model=dict, summary="创建新用户")
async def create_user(user: User):return await user_service.create_user(user)@router.get("/users/", response_model=UserResponse, summary="获取用户列表")
async def get_users(page: int = Query(1, ge=1),page_size: int = Query(10, ge=1)):skip = (page - 1) * page_sizereturn await user_service.get_users(skip, page_size)@router.put("/users/{id}", response_model=dict, summary="更新用户信息")
async def update_user(id: str, user: User):return await user_service.update_user(id, user)@router.delete("/users/{id}", response_model=dict, summary="删除用户")
async def delete_user(id: str):return await user_service.delete_user(id)@router.get("/users/{id}", response_model=User, summary="获取单个用户信息")
async def get_user(id: str):return await user_service.get_user(id)

2.9 入口程序

main.py

from fastapi import FastAPI
from app.controller.user_controller import router as user_router
from app.middleware import setup_middlewares# 创建app
app = FastAPI(title="用户管理API",description="提供用户增删改查和分页查询功能",version="1.0.0",
)# 初始化中间件
setup_middlewares(app)# 注册路由
app.include_router(user_router, tags=["用户管理"])if __name__ == '__main__':import uvicornuvicorn.run(app, host="127.0.0.1", port=8080)

总结

源滚滚编程提供全套的PDF文档,配套源代码,录播课,私教课和直播课,关注并私信我咨询获取。

相关文章:

  • [Java实战]Spring Boot 解决跨域问题(十四)
  • 聊一聊常见的超时问题:timeout
  • 解决stm32HAL库使用vscode打开,识别不到头文件及uint8_t等问题
  • pandas读取pymysql和解析excel的一系列问题(版本不匹配)
  • 单细胞RNA测序数据分析与可视化:从基础原理到高级应用
  • 2. cef 及 cefcapi
  • OB Cloud 云数据库V4.3:SQL +AI全新体验
  • Python训练营打卡——DAY22(2025.5.11)
  • 【计算机网络】网络IP层
  • 【Linux系统】第四节—详解yum+vim
  • OpenCV进阶操作:指纹验证、识别
  • MySQL 数据库集群部署、性能优化及高可用架构设计
  • 鱼眼相机生成-BEV鸟瞰图-入门教程
  • 设计模式简述(十九)桥梁模式
  • Playwright 简介
  • 探索虚拟化:云计算时代的资源优化之道
  • MCP Streamable HTTP 传输层的深度解析及实战分析
  • 前端npm包发布流程:从准备到上线的完整指南
  • 虚拟内存:深入解析与性能优化
  • 5G赋能应急响应:让救援更快、更智能、更精准
  • 上海能源科技发展有限公司原董事长李海瑜一审获刑13年
  • 美英贸易协议|不,这不是一份重大贸易协议
  • 哈佛新论文揭示 Transformer 模型与人脑“同步纠结”全过程!AI也会犹豫、反悔?
  • 牛市早报|中美经贸高层会谈达成重要共识,取得实质性进展
  • 重庆三峡学院回应“85万元中标设备,网购价不到300元”:已着手解决
  • 观察|天空之外的战场:官方叙事、新闻与社交平台中的印巴冲突