@router.get("/list", summary="查看用户列表")
async def list_user(
page: int = Query(1, description="页码"),
page_size: int = Query(10, description="每页数量"),
username: str = Query("", description="用户名称,用于搜索"),
email: str = Query("", description="邮箱地址"),
dept_id: int = Query(None, description="部门ID"),
):
q = Q()
if username:
q &= Q(username__contains=username)
if email:
q &= Q(email__contains=email)
if dept_id is not None:
q &= Q(dept_id=dept_id)
total, user_objs = await user_controller.list(page=page, page_size=page_size, search=q)
data = [await obj.to_dict(m2m=True, exclude_fields=["password"]) for obj in user_objs]
for item in data:
dept_id = item.pop("dept_id", None)
item["dept"] = await (await dept_controller.get(id=dept_id)).to_dict() if dept_id else {}
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
1. 路由定义
@router.get("/list", summary="查看用户列表")
-
这行代码定义了一个 GET 请求的路由,路径为
/list
,用于获取用户列表信息。summary
参数用于在 API 文档中简要描述该接口的功能,这里描述为 “查看用户列表”。
2. 函数定义
async def list_user(
-
定义了一个异步函数
list_user
,因为 FastAPI 支持异步操作,使用async
和await
可以提高程序的性能和响应速度,特别是在涉及 I/O 操作如数据库查询时。
3. 参数定义
page: int = Query(1, description="页码"),
-
定义了一个整型参数
page
,默认值为 1。Query
是 FastAPI 中用于定义查询参数的函数,description
参数用于在 API 文档中对该参数进行详细描述,这里表示该参数是用于分页的页码。
page_size: int = Query(10, description="每页数量"),
-
定义了一个整型参数
page_size
,默认值为 10,用于指定每页显示的数量,同样使用Query
进行定义并添加描述。
username: str = Query("", description="用户名称,用于搜索"),
-
定义了一个字符串参数
username
,默认值为空字符串,用于根据用户名称进行搜索。
email: str = Query("", description="邮箱地址"),
-
定义了一个字符串参数
email
,默认值为空字符串,用于根据邮箱地址进行搜索。
dept_id: int = Query(None, description="部门ID"),):
-
定义了一个整型参数
dept_id
,默认值为 None,用于根据部门 ID 进行筛选。
4. 查询条件构造
q = Q()
-
初始化一个查询条件对象
q
,Q
通常是一个用于构建复杂查询条件的类,在 ORM(如 Tortoise-ORM)中常用到,用于组合多个查询条件。
if username:
q &= Q(username__contains=username)
if email:
q &= Q(email__contains=email)
if dept_id is not None:
q &= Q(dept_id=dept_id)
-
这几行代码根据传入的参数构建查询条件。
&=
运算符用于将多个条件进行组合,表示 “与” 的关系。 -
如果
username
不为空,则添加一个条件,要求用户名包含传入的username
值。 -
如果
email
不为空,则添加一个条件,要求邮箱地址包含传入的email
值。 -
如果
dept_id
不为 None,则添加一个条件,要求部门 ID 等于传入的dept_id
值。
5. 调用控制器获取数据
total, user_objs = await user_controller.list(page=page, page_size=page_size, search=q)
-
调用
user_controller
中的list
方法,传入分页参数page
、page_size
和构建好的查询条件q
,获取用户列表数据和总记录数total
。await
关键字用于等待异步操作完成,因为list
方法可能涉及到异步的数据库查询操作。
6. 数据处理
data = [await obj.to_dict(m2m=True, exclude_fields=["password"]) for obj in user_objs]
-
使用列表推导式对获取到的用户对象
user_objs
进行处理,将每个对象转换为字典格式。await obj.to_dict(m2m=True, exclude_fields=["password"])
表示调用对象的to_dict
方法,m2m=True
可能表示包含多对多关系的数据,exclude_fields=["password"]
则表示在转换时排除密码字段,以保护用户密码信息不被泄露。
7. 补充部门信息
for item in data:
dept_id = item.pop("dept_id", None)
item["dept"] = await (await dept_controller.get(id=dept_id)).to_dict() if dept_id else {}
-
遍历处理后的用户数据列表
data
。 -
item.pop("dept_id", None)
用于获取并移除用户数据中的dept_id
字段,避免重复数据并提高数据传输效率。 -
如果
dept_id
存在,则通过dept_controller.get(id=dept_id)
获取对应的部门对象,并调用其to_dict
方法转换为字典格式,作为用户的部门信息存储到item["dept"]
中;如果dept_id
不存在,则将item["dept"]
设置为一个空字典。
8. 返回响应
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
-
返回一个
SuccessExtra
类型的响应对象,包含用户数据列表data
、总记录数total
、当前页码page
和每页数量page_size
,用于向前端提供分页后的用户列表数据以及分页相关信息,方便前端进行分页展示等操作。
在实际开发中,还有一些实用技巧可以帮助新手更好地理解和运用代码:
前端调用示例
在 Vue3 中,可以使用 axios
来调用该接口获取用户列表数据:
import axios from 'axios';
const getUserList = async (page = 1, page_size = 10, username = '', email = '', dept_id = null) => {
try {
const response = await axios.get('/api/list', {
params: { page, page_size, username, email, dept_id }
});
return response.data;
} catch (error) {
console.error('获取用户列表失败:', error);
return null;
}
};
在组件中调用该函数获取数据后,可以使用 NaiveUI 的表格组件 n-data-table
来展示用户列表数据,并结合分页组件 n-pagination
实现分页功能:
<template>
<div>
<n-data-table :columns="columns" :data="userList" />
<n-pagination v-model:page="currentPage" :page-count="totalPages" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getUserList } from './api/userApi';
const userList = ref([]);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(10);
const columns = [
{ title: '用户名', key: 'username' },
{ title: '邮箱', key: 'email' },
{ title: '部门', key: 'dept.name' }, // 假设部门信息中有 name 字段
];
onMounted(async () => {
await fetchUserList();
});
const fetchUserList = async () => {
const data = await getUserList(currentPage.value, pageSize.value);
if (data) {
userList.value = data.data;
total.value = data.total;
}
};
// 分页切换时重新获取数据
watch(currentPage, fetchUserList);
</script>
数据验证
在 FastAPI 中,可以使用 Pydantic 模型对请求参数进行验证,确保接收到的参数符合预期的格式和类型。例如,可以定义一个请求模型:
from pydantic import BaseModel, Field
class UserListQuery(BaseModel):
page: int = Field(1, ge=1, description="页码,必须大于等于1")
page_size: int = Field(10, ge=1, le=100, description="每页数量,必须大于等于1且小于等于100")
username: str = Field("", max_length=50, description="用户名称,最大长度50")
email: str = Field("", description="邮箱地址")
dept_id: int = Field(None, description="部门ID")
然后在路由函数中使用该模型作为参数:
@router.get("/list", summary="查看用户列表")
async def list_user(query: UserListQuery = Depends()):
q = Q()
if query.username:
q &= Q(username__contains=query.username)
if query.email:
q &= Q(email__contains=query.email)
if query.dept_id is not None:
q &= Q(dept_id=query.dept_id)
total, user_objs = await user_controller.list(page=query.page, page_size=query.page_size, search=q)
# 后续代码不变
这样可以更好地对请求参数进行验证和管理,提高代码的健壮性和可维护性。
错误处理
在实际开发中,可能会出现各种错误情况,如数据库查询失败、网络请求超时等。为了更好地处理这些错误,可以使用 FastAPI 的异常处理机制。例如,定义一个自定义异常类:
from fastapi import HTTPException
class UserNotFoundError(HTTPException):
def __init__(self):
super().__init__(status_code=404, detail="用户未找到")
在代码中,如果出现用户未找到的情况,可以抛出该异常:
user = await User.get(id=user_id)
if not user:
raise UserNotFoundError()
在前端,可以对返回的错误信息进行统一处理,给用户友好的提示:
axios.get('/api/list', { params })
.then(response => {
// 处理成功情况
})
.catch(error => {
if (error.response) {
// 服务器返回了错误响应
console.error('错误状态码:', error.response.status);
console.error('错误信息:', error.response.data.detail);
// 根据不同的错误状态码和信息,给用户相应的提示
} else if (error.request) {
// 请求发送失败(如网络问题)
console.error('请求发送失败:', error.message);
} else {
// 请求配置错误等其他情况
console.error('请求配置错误:', error.message);
}
});