0 环境
- 使用的系统:mac
- 使用的框架:FastSoyAdmin
- 使用的数据库:PostgreSQL
- 技术:fastapi、vue3
1 导出功能描述
导出流程就是点击导出按钮,然后把我想要的前端展示的表格数据下载到本地,或者说在导出时,还有其他的数据处理诉求等,但本质上就是将这些数据洗洗干净放到excel中供用户下载到本地使用。知道干啥了,那么这个导出的活该谁做呢。

2 后端导出报错
后端导出其实前后端都要做处理,后端要从数据库里拿到相应的data,然后生成表格(使用openpyxl或者xlsxwriter库),然后在用
StreamingResponse
方式将excel返回给你前端,前端使用blob方式下载(封装好了就调用就好了)。我遇到的坑是明明都默认是utf-8,导出流时报错。
1 后端导出处理思路复盘
这里我就遇到一个坑,当我用openpyxl 生成excel后,都没问题,直到return StreamingResponse xxx
发给前端时,会报错 'latin-1' codec can't encode characters in position 41-54: ordinal not in range(256)。排查如下:
1 mac语言环境排查
一开始我怀疑是不是mac默认语言环境的问题,查看了 echo $LANG 和 locale ,也是有utf-8的。重设了语言环境,
bash
echo 'export LANG="en_US.UTF-8"' >> ~/.zshrc
source ~/.zshrc
locale charmap


2 ide排查
默认环境是utf-8,这里我又重新设置了一遍了。
3 数据库排除
打开cli客户端,找到对应数据库,右键编辑数据库,找到字符分类,也是utf-8 ,没有问题呀。

为了以防万一,我又用命令查了一遍。复制如下代码:
sql
-- 验证数据库编码
SELECT datname, pg_encoding_to_char(encoding)
FROM pg_database WHERE datname = current_database();
-- 验证服务器编码
SHOW server_encoding;
-- 验证客户端编码
SHOW client_encoding;
-- 验证当前会话编码
SELECT pg_client_encoding();
可以看到也是utf-8格式的,没有问题的。其实这里最保险起见还是换个mysql、SQLite这类的都试下。

4 连接postgresql库排查
有人说是连接库的有问题,url里强制加入utf-8,在测还是报错,我直接utf-8 格式先写入到本地,打开发现也没问题,说明连接这个库也是么有问题的。StreamingResponse 也设置过了utf-8。
3 解决替代方案
最后换到云服务器测,就没问题。虽然没找到真正原因,还是准备了替代方案。
1 前端导出
假如数据量不大的话,直接问ai,前端xlsx和file-saver导出excel文件,别忘了让后端先判断有导出这个权限后,然后再给你数据。但是哪天保不齐某个用户不单单只要几条,ta突发奇想我全都要导出,数据量还可以的情况,前后端谁做都是可以的,但一定要从后端拿到全部data数据,然后前端做处理生成excel。注意啦,假如导出数据量不大的话,python + tortoise orm完全可以的,可一旦数据量多了 + 有判断处理的要求的时候,请用go rust这类的编程语言作为底层帮你专门处理数据,当然也可以在数据库层面想办法,比如写存储过程这类的也可以的,不然直接用python的orm,会卡的要死。
1 xlsx和file-saver导出excel
安装xlsx和file-saver依赖 pnpm add xlsx file-saver @types/xlsx @types/file-saver
exportToExcel需要的参数:table数据, 文件名,表头。
js
import { saveAs } from 'file-saver';
import * as XLSX from 'xlsx';
/**
* 导出数据到Excel文件
* @param data 要导出的数据数组
* @param filename 导出的文件名(不含扩展名)
* @param sheetName 工作表名称
*/
export function exportToExcel<T extends Record<string, any>>(
data: T[],
filename: string = '导出数据',
sheetName: string = 'Sheet1'
): void {
try {
// 创建工作簿
const workbook = XLSX.utils.book_new();
// 将数据转换为工作表
const worksheet = XLSX.utils.json_to_sheet(data);
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
// 生成Excel文件二进制数据
const excelBuffer = XLSX.write(workbook, {
bookType: 'xlsx',
type: 'array'
});
// 创建Blob并保存文件
const blob = new Blob([excelBuffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
saveAs(blob, `${filename}.xlsx`);
} catch (error) {
console.error('导出Excel失败:', error);
throw new Error('导出Excel文件时出错');
}
}
假如没有导出权限的要求时,先判断有没有数据,在调用exportToExcel(最好这里的数据是从后端拿的好),若是后端你也要写的话,也请别忘了在路由中加入dependencies=[DependPermission]
,在这个导出路由里,根据前端请求获取数据库的数据,整成个数组发给前端,前端exportToExcel(后端发来的data, {表格名,表头})。
js
<script setup lang="ts">
import { ref } from 'vue';
import { exportToExcel } from '@/utils/excelExporter';
const isLoading = ref(false);
const tableData = ref<Array<Record<string, any>>>([]);
// 模拟获取数据
const fetchData = async () => {
// 实际项目中替换为API调用
return [
{ id: 1, product: '商品A', price: 100, stock: 50 },
{ id: 2, product: '商品B', price: 200, stock: 30 }
];
};
const handleExport = async () => {
try {
isLoading.value = true;
// 如果没有数据,先获取数据
if (tableData.value.length === 0) {
tableData.value = await fetchData();
}
exportToExcel(tableData.value, {
filename: '商品列表',
headers: {
id: 'ID',
product: '商品名称',
price: '价格',
stock: '库存'
}
});
} catch (error) {
console.error('导出失败:', error);
} finally {
isLoading.value = false;
}
};
</script>
<template>
<button @click="handleExport" :disabled="isLoading">
{{ isLoading ? '导出中...' : '导出Excel' }}
</button>
</template>
2 后端生成静态文件,前端访问对应url
其实就是后端挂载的static那个目录,我们将写入的excel文件最后放在这个目录里即可,最后前端根据url(ip:端口/static//xxx.xlsx),访问这个目录即可,比如get请求时带入
responseType: 'blob'
,然后用file-saver
里的saveAs
下载文件即可,saveAs保存的两个入参,一个是:填入blob那个请求,拿到返回的data,第二个就是:定义文件名,目前测试效果下也很不错。你要是不想用第三库,就用原生的方式,拼个a标签,然后将url和文件名塞入进去也可以的,当然要求高的话,还是使用专业的oss存储,我是打算有机会用rustfs**存储excel。
4 小结
根据后台显示的报错,排除所有可能的情况,挨个排查一遍后,还是无果,还可以试着换个window或者打包放到linux环境跑下,说不定你和你的代码都没问题呢。当然为了保险起见,还是要留些替代方案,比如前端导出的方案,或者后端生成Excel(文件名最好加个时间戳哦)在放到静态目录中,然后前端访问静态文件的方式下载等等方案。