Skip to content

导入导出与文档 ​

概述 ​

后端在「导入导出」与「文档生成」方面的框架能力:基于 openpyxl / pandas 的 Excel 工具,以及基于 Jinja2 / WeasyPrint 的 PDF 工具。二者都位于 app/framework/common/ 下,配合 Web 层的 StreamResponse 即可向前端返回文件流。

Excel 与 PDF 这两类能力在系统里用途不同,但实现思路一致:后端生成字节流 → 通过流式响应返回 → 前端触发下载或上传。

能力模块依赖典型场景
Excel 导入导出framework/common/excel.pyopenpyxl、pandas用户、角色、字典、配置等批量导入导出
PDF 生成framework/common/pdf.pyjinja2、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 响应头,否则前端可能拿到乱码文件名。