外观
导入导出与文档
概述
后端在「导入导出」与「文档生成」方面的框架能力:基于 openpyxl / pandas 的 Excel 工具,以及基于 Jinja2 / WeasyPrint 的 PDF 工具。二者都位于 app/framework/common/ 下,配合 Web 层的 StreamResponse 即可向前端返回文件流。
Excel 与 PDF 这两类能力在系统里用途不同,但实现思路一致:后端生成字节流 → 通过流式响应返回 → 前端触发下载或上传。
| 能力 | 模块 | 依赖 | 典型场景 |
|---|---|---|---|
| Excel 导入导出 | framework/common/excel.py | openpyxl、pandas | 用户、角色、字典、配置等批量导入导出 |
| PDF 生成 | framework/common/pdf.py | jinja2、weasyprint | 报表、票据、协议等可打印文档 |
app/framework/web/response.py 中的 StreamResponse 是对 FastAPI StreamingResponse 的封装,专门用于返回二进制文件流;文件落盘场景则可用 UploadFileResponse。
Excel 导入导出
核心类为 ExcelUtil,提供三类方法:生成模板、列表导出、以及配合业务层使用 pandas 解析上传文件。
生成导入模板
get_excel_template 用于生成一份带表头和下拉选项的空 Excel,供用户下载后填写。它对表头单元格做灰色填充与居中,并对指定列添加下拉数据校验(DataValidation),从源头减少填写错误。
python
ExcelUtil.get_excel_template(
header_list=["部门编号", "账号", "昵称", "邮箱", "手机号", "性别", "状态"],
selector_header_list=["性别", "状态"],
option_list=[{"性别": ["男", "女"]}, {"状态": ["正常", "停用"]}],
data_list=[["100", "huawei", "华为", "hw@hw.com", "13812345678", "男", "正常"]],
)header_list:表头顺序,决定列顺序。selector_header_list+option_list:需要下拉选项的列及其可选项。data_list(可选):示例数据行,便于用户理解格式。
列表导出
export_list2excel 接收「已是中文表头」的字典列表与字段映射,借助 pandas.DataFrame 写出 Excel 字节流。业务层通常先完成「枚举值 → 可读文本」「字段名 → 中文表头」的转换,再交给它写出。
python
mapping = {"username": "账号", "nickname": "昵称", "sex": "性别"}
ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping)解析导入文件
导入方向没有放进 ExcelUtil,而是在各业务 Service 里直接用 pandas.read_excel 解析上传文件,并按表头把每一行映射成业务数据。这样可以把「字段校验、部门/角色关联、事务隔离」等业务逻辑留在业务层。
python
contents = await file.read()
df = pd.read_excel(io.BytesIO(contents))
await file.close()与 Web 层集成
导出/模板接口返回 StreamResponse,关键三点:
- 用
bytes2file_response(bytes)把字节包成一个生成器作为响应体; - 设置正确的
media_type(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); - 在
Content-Disposition里指定下载文件名,含中文时记得urllib.parse.quote编码,并暴露该响应头供前端读取。
python
return StreamResponse(
data=bytes2file_response(result),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={
"Content-Disposition": f"attachment; filename={urllib.parse.quote('用户导入模板.xlsx')}",
"Access-Control-Expose-Headers": "Content-Disposition",
},
)实战:用户导入导出
以「系统管理-用户管理」为例,三种能力串成一条完整链路:
要点:
- 导入用
UploadFile接收文件,updateSupport表单字段控制「账号已存在时是否更新」。 - 逐行写入使用数据库
SAVEPOINT隔离:单行失败只回滚当前 savepoint,不影响其余行,也避免事务关闭导致后续行全部失败。 - 导入结果返回成功条数、失败条数及逐行错误明细;部分失败时使用错误码提示(见
web/response的RET)。
PDF 文档生成
核心类为 PdfUtil,基于 Jinja2 模板渲染 HTML,再用 WeasyPrint 把 HTML/CSS 转成 PDF。适合合同、报表、票据等需要精确排版的场景。
渲染模板
render_html_template 用 FileSystemLoader 加载 Jinja2 模板,StrictUndefined 保证变量缺失即报错(避免静默生成空白文档),并开启 autoescape 防注入。
python
html_str = PdfUtil.render_html_template(
template_name="welcome.jinja2",
template_dir="app/framework/common/templates",
variables={"name": "张三", "date": "2026-10-02"},
)模板文件建议统一放在 framework/common/templates/ 目录(当前内置 welcome.jinja2 作为示例)。
HTML 转 PDF
html_to_pdf 调用 WeasyPrint 把 HTML 字符串写为 PDF 字节流,可附加一段 CSS 字符串控制打印样式(页边距、分页、字体等)。
python
pdf_bytes = PdfUtil.html_to_pdf(html_str, css_str=css)保存与一站式生成
save_pdf:把字节流写入磁盘,自动创建父目录。generate_pdf_from_template:模板渲染 → 转 PDF → 落盘 一步到位,适合「后端生成后存储到文件服务」的场景。
python
PdfUtil.generate_pdf_from_template(
template_name="welcome.jinja2",
template_dir="app/framework/common/templates",
variables={"name": "张三"},
output_path="static/pdf/welcome.pdf",
)生成链路
提示:
WeasyPrint依赖系统级图形库(如cairo、pango),容器部署时需在镜像里安装对应系统包,否则转换会失败。
前端对接
前端通过 requestClient 的 download / upload 封装与后端文件接口对接,无需关心 Content-Disposition 解析。
下载(导出 / 模板)
typescript
// 导出用户
export function exportUser(params: any) {
return requestClient.download('/system/user/export-excel', { params });
}
// 下载导入模板
export function importUserTemplate() {
return requestClient.download('/system/user/get-import-template');
}上传(导入)
typescript
export function importUser(file: File, updateSupport: boolean) {
return requestClient.upload('/system/user/import', { file, updateSupport });
}交互范式
- 导入弹窗(
import-form.vue)中先用ElUpload(auto-upload=false)选择.xlsx文件,提交时调用importUser。 - 「下载导入模板」按钮调用
importUserTemplate,再用downloadFileFromBlobPart触发浏览器下载。 - 导出按钮直接调用
exportUser,requestClient.download会自动解析响应流并保存为文件。
最佳实践与注意事项
- 表头与枚举文本用中文、字段名用英文:导出时做「字段名→中文表头、枚举→可读文本」映射,导入时反向解析,避免把内部枚举值直接暴露给用户。
- 大文件导出用分页或流式:单页数据量过大时先按条件查询再导出,必要时分片,避免内存与超时问题。
- 导入做行级事务隔离:用
SAVEPOINT保证单行失败不影响整体,并向用户返回可定位的错误明细。 - 模板与下拉选项对齐:模板的下拉选项、
option_list必须与导入解析时的取值严格一致(如「正常/停用」)。 - PDF 模板集中管理:模板放
framework/common/templates/,用StrictUndefined及早暴露缺参;生产环境确认WeasyPrint的系统依赖齐全。 - 文件名编码:含中文的文件名务必
urllib.parse.quote并暴露Content-Disposition响应头,否则前端可能拿到乱码文件名。