日前,笔者项目有一个导出JSON和Excel文件的需求,碰到一个很奇怪的现象,排查了两天,终于搞懂了,现在复盘整个过程和解决方法。
一、简要代码
Django-Vue3前后端分离项目,后端采用Django REST Framework (DRF) 框架。 针对导出JSON和Excel文件的需求,我的设计如下: 1、前端接口定义:
typescript
export interface InfosExportParams {
format: "json" | "excel"; // 导出格式
name?: string; // 按信息名称模糊搜索
category?: number; // 按信息类别ID精确匹配
}
2、前端网络请求API
csharp
export const exportInfos = async (params?: InfosExportParams): Promise<any[]> => {
return request.get("/infos/export/", {
params,
responseType: "json",
});
};
3、前端导出功能调用
typescript
// 导出按钮click函数,传递"json" | "excel"参数
const handleExport = async (formatParams: "json" | "excel") => {
// 构建导出参数 - 使用当前筛选条件
const exportParams: InfosExportParams = {
format: formatParams,
};
if (searchForm.name) exportParams.name = searchForm.name;
if (searchForm.category) exportParams.category = searchForm.category;
if (formatParams === "json") {
// 导出JSON格式
try {
const flatJSONData = await exportInfos(exportParams);
// 由前端继续处理flatJSONData数据,导出JSON文件
// 后续省略
} catch (error: any) {
// 省略
}
} else if (formatParams === "excel") {
// 导出Excel格式
try {
const flatJSONData = await exportInfos(exportParams);
// 由前端继续处理flatJSONData数据,导出Excel文件
// 后续省略
} catch (error: any) {
// 省略
}
}
};
4、后端采用自定义action:GET /api/infos/export/ 在视图集里定义如下:
python
# 自定义 action:GET /api/infos/export/ - 导出扁平化 JSON 数据
@action(detail=False, methods=["get"], url_path="export")
def infos_export(self, request):
print("开始生成扁平化 JSON 数据...")
// 处理请求,返回响应。省略
return response
以上这些代码,是我反复排查后,确认的和Bug可能有关的核心代码,移除了无关的业务逻辑。
二、现象描述
- 1、当参数 format 为 "json"时,可以正常返回数据;
- 2、当参数 format 为 "excel"时,返回404错误"请求的资源不存在",而且连自定义action infos_export方法都没有进去(第一行print调试语句没有打印)。
以上就是我排查整个前后端功能链路上聚焦的核心关键点。
我一开始以为是后端的权限配置问题,或者url设置问题,但反复测试下来不是。
后来发现,在排除掉无关业务代码后,两种导出功能在前后端的代码几乎一模一样,只有format参数不一样。
这时我还没有怀疑是format参数的问题,总认为format参数是我自定义参数的一个字段,Django/DRF难道能够针对GET方法,自动解析我的参数 format,并根据解析结果产生不同行为?
三、定位分析
这种现象太奇怪了,反复排查,移除无关代码,最后让我不得不怀疑就是format参数的问题,尽管format是我自定义的一个参数。
实际上,DRF为了提供开箱即用的"可浏览API"(Browsable API)功能,内置了一个机制,它会检查请求中的format查询参数,并根据其值来决定使用哪种渲染器(Renderer)来生成响应。
当我使用request.get("/infos/export/", { params: { format: "excel" } }) 发送请求时,DRF在视图函数执行之前,会先处理这个format参数。
而DRF的核心库中并不包含名为 ExcelRenderer 的内置渲染器。DRF 默认只提供了以下几种渲染器:
- JSONRenderer (默认)
- BrowsableAPIRenderer
- AdminRenderer
- HTMLFormRenderer
- XMLRenderer
因此,如果我想导出 Excel 文件,DRF无法找到处理 format=excel的方式。因此,它会认为这个请求无效,并直接返回一个404 Not Found响应,我的自定义action infos_export根本没有机会执行。
这就是为什么request.get("/infos/export/", { params: { format: "json" } })成功,request.get("/infos/export/", { params: { format: "excel" } })失败的根本原因。
解决方法也很简单,直接更换参数名,将前端的参数名从 format 改为如 export_format 。
四、大模型AI的作用
整个问题的排查分析,我让AI深度参与,国内trae、千问、deepseek试了个遍,都没有用,认为就是我后端的权限配置或URL设置的问题。
当我指出,很可能是format参数的问题时,依然从权限和url角度排查,要么陷入死循环,要么给出错误结论然后停摆。
最后是我查阅DRF官方文档,看到DRF会对format参数进行自动解析时,AI才接受,后面就顺利了。