easyexcel-fill

基于 Alibaba EasyExcel(com.alibaba:easyexcel:4.0.3)的 Excel 模板填充技能。覆盖简单对象填充、列表填充({.}+forceNewRow)、横向多列组合填充(FillWrapper+WriteDirectionEnum.HORIZONTAL)、复杂填充(含合计行/统计行/先 fill 后 write)、Spring Boot 集成、模板设计规范、转义字符、03版/07版差异、性能优化(分次 fill、文件缓存)。当用户需要把 Java 对象或 Map 数据按预制 .xlsx 模板(变量语法 {var}、{.}、{prefix.})填充生成报表(工资条/合同/对账单/发票/财务报表/统计表/汇总表)时使用此技能,不适用于不基于模板的"程序生成 Excel"场景(改用 easyexcel-write)。

full-stack-skills/java-skills34 installsApache-2.0Synced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: easyexcel-fill
description: 基于 Alibaba EasyExcel(com.alibaba:easyexcel:4.0.3)的 Excel 模板填充技能。覆盖简单对象填充、列表填充({.}+forceNewRow)、横向多列组合填充(FillWrapper+WriteDirectionEnum.HORIZONTAL)、复杂填充(含合计行/统计行/先 fill 后 write)、Spring Boot 集成、模板设计规范、转义字符、03版/07版差异、性能优化(分次 fill、文件缓存)。当用户需要把 Java 对象或 Map 数据按预制 .xlsx 模板(变量语法 {var}、{.}、{prefix.})填充生成报表(工资条/合同/对账单/发票/财务报表/统计表/汇总表)时使用此技能,不适用于不基于模板的"程序生成 Excel"场景(改用 easyexcel-write)。
license: Apache-2.0
---

# EasyExcel 模板填充(Fill)

> 官方文档:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill
> 官方示例:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java
> 当前版本:`com.alibaba:easyexcel:4.0.3`(Apache-2.0,仓库已归档,仅做 Bug 修复)

本技能专注于 **"按预制模板填充"** 场景。当用户给出一份已经画好样式的 `.xlsx` 模板,要求把数据库/接口里的数据填进去(工资条、合同、发票、报表、统计单等),应当使用本技能。

## Capability Boundaries

### ✅ 强项
1. 简单对象填充(Map / 单个 DTO)
2. 列表填充(模板 `{.}` 语法,单向纵向)
3. 复杂填充(list 后还有内容 → `forceNewRow=true`)
4. 大数据量复杂填充(list 必须放最后一行,后续统计行用 `write` 追加)
5. 横向多列组合填充(`WriteDirectionEnum.HORIZONTAL` + `FillWrapper`)
6. 模板转义字符处理(`\{` `\}`)
7. Spring Boot 集成(模板文件可来自 resources/ 或 OSS)

### ⚠️ 限制
1. 仅支持 `.xlsx`(07 版),03 版(`.xls`)不支持复杂/大数据量填充
2. `forceNewRow=true` 会全量驻内存,数据量大时慎用
3. `withTemplate` 模板会全量加载到内存,模板文件过大(>几十 MB)会 OOM
4. Map 填充 list 时必须包含所有 list 的 key,否则报 NPE

### ❌ Out of Scope(不该用本技能的场景,请改用其它技能)
1. **不基于模板的程序化生成 Excel**(无预制模板)→ **不适用**本技能,使用 `easyexcel-write`
2. **读取 Excel 数据** → **不适用**本技能,使用 `easyexcel-read`
3. **CSV 文件处理** → **不适用**本技能,自行使用 commons-csv / OpenCSV
4. **Word/PPT** → **不适用**本技能,使用 Apache POI / Apache POI-XML

## Data Privacy

本技能不收集、存储或传输任何用户数据。所有代码示例仅用于本地开发参考。

## When to use this skill

- 用户说"按模板生成 Excel"、"填充 Excel 模板"、"导出工资条/合同/发票"
- 用户提供 `.xlsx` 模板文件,要求把数据写进变量占位符
- 报表存在固定格式,需要保留样式/合并单元格/函数/图表
- 业务上有"先 fill 列表,再追加合计行/统计行"的需求

## Quick Start

**典型调用方式(4 选 1,按文件大小递增)**:

```java
// 方案 A:最简单的对象填充(since 2.1.1)
String fileName = "output_simple.xlsx";
EasyExcel.write(fileName).withTemplate("templates/simple.xlsx")
    .sheet().doFill(fillData);

// 方案 B:列表填充(一次放入内存)
EasyExcel.write(fileName).withTemplate("templates/list.xlsx")
    .sheet().doFill(list);

// 方案 C:列表分次填充(文件缓存,省内存)
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
        .withTemplate("templates/list.xlsx").build()) {
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    excelWriter.fill(list, writeSheet);  // 多次 fill,自动文件缓存
    excelWriter.fill(list, writeSheet);
}

// 方案 D:先 fill 列表 + 后 write 追加合计行(推荐:复杂报表 + 大数据量)
try (ExcelWriter excelWriter = EasyExcel.write(fileName)
        .withTemplate("templates/complex.xlsx").build()) {
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    excelWriter.fill(list, writeSheet);          // 1. 填列表
    excelWriter.fill(headerMap, writeSheet);     // 2. 填头部变量
    excelWriter.write(totalListList, writeSheet);// 3. 手动追加合计行
}
```

## Workflow

Step 1. **确认场景** — 用户是否提供了 `.xlsx` 模板?模板里是否有 `{var}` `{.}` `{prefix.}` 占位符?
Step 2. **评估数据量** — < 1 万行:方案 A/B;> 1 万行:方案 C/D(list 必须放最后一行)
Step 3. **选择填充模式** — 单对象 / 列表 / 复杂 / 横向多列
Step 4. **构造数据** — Map / DTO / List;Map 填充 list 必须包含所有 key
Step 5. **执行填充** — try-with-resources 关闭 `ExcelWriter`
Step 6. **验证输出** — 打开生成的 `.xlsx` 检查占位符是否被替换、有无残留 `{.}` `{}`

## Critical: 模板语法速查

| 模板占位符 | 含义 | Java 数据结构 |
|------------|------|---------------|
| `{name}` `{number}` `{date}` | 单值占位符 | `Map.put("name", value)` 或 DTO 字段 |
| `{.}` | 列表占位符(默认纵向) | `List<DTO>` 整体作为 fill 第一个参数 |
| `{data1.}` `{data2.}` | 带前缀的列表(多列表区分) | `new FillWrapper("data1", list)` |
| `\{` `\}` | 转义:当模板中真有 `{` `}` 字符时 | —— |

> **官方原文**:"{} 普通变量,{.} list 的变量。如果本来就有'{'、'}' 特殊字符 用'\\{'、'\\}' 代替。{.前缀可以区分不同的list。"

## Critical: 4 种填充场景完整示例

### 场景 1:简单对象填充

**DTO**:
```java
@Getter
@Setter
@EqualsAndHashCode
public class FillData {
    private String name;
    private double number;
    private Date date;
}
```

**模板**(`templates/simple.xlsx`):单元格含 `{name}` `{number}` `{date}`

**代码**:
```java
@Test
public void simpleFill() {
    String templateFileName = "templates/simple.xlsx";
    String fileName = "output_simpleFill_" + System.currentTimeMillis() + ".xlsx";

    // 方案 1:根据对象填充
    FillData fillData = new FillData();
    fillData.setName("张三");
    fillData.setNumber(5.2);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);

    // 方案 2:根据 Map 填充
    Map<String, Object> map = new HashMap<>();
    map.put("name", "张三");
    map.put("number", 5.2);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
```

### 场景 2:列表填充(模板 `{.}` 占位一行)

**模板**(`templates/list.xlsx`):一行单元格含 `{.}` 表示 list 起点

**代码**:
```java
@Test
public void listFill() {
    String templateFileName = "templates/list.xlsx";
    String fileName = "output_listFill_" + System.currentTimeMillis() + ".xlsx";

    // 方案 1:一次性放入内存
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data());

    // 方案 2:分次填充(文件缓存,省内存,推荐)
    try (ExcelWriter excelWriter = EasyExcel.write(fileName)
            .withTemplate(templateFileName).build()) {
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        excelWriter.fill(data(), writeSheet);
        excelWriter.fill(data(), writeSheet);
    }
}
```

> **官方提示**:"分多次 填充 会使用文件缓存"

### 场景 3:复杂填充(list 后还有合计行/总计行)

**场景特征**:模板中 `{.}` 列表行下面还有非列表内容(如"总计:xxx")。

```java
@Test
public void complexFill() {
    String templateFileName = "templates/complex.xlsx";
    String fileName = "output_complexFill_" + System.currentTimeMillis() + ".xlsx";

    try (ExcelWriter excelWriter = EasyExcel.write(fileName)
            .withTemplate(templateFileName).build()) {
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        // ⚠️ 关键:forceNewRow=true 才能让 list 后面再追加内容
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

        excelWriter.fill(data(), fillConfig, writeSheet);
        excelWriter.fill(data(), fillConfig, writeSheet);

        Map<String, Object> map = new HashMap<>();
        map.put("date", "2019年10月9日13:28:28");
        map.put("total", 1000);
        excelWriter.fill(map, writeSheet);
    }
}
```

> **官方原文**:"在 02 版上面 模板 list 不是最后一行 下面还有数据 需要设置 `forceNewRow=true` 但是有一个缺点 就是他会把所有的数据都放到内存了 慎用"
> **官方原文**:"如果数据量大 list不是最后一行 参照下一个(complexFillWithTable)"

### 场景 4:数据量大的复杂填充(list 放最后一行 + write 追加)

**最佳实践模板设计**:删除模板中 list 之后的"合计行"占位行,让 list 占最后一行;后续"统计行"用 `write` 手动追加。

```java
@Test
public void complexFillWithTable() {
    String templateFileName = "templates/complexFillWithTable.xlsx";
    String fileName = "output_complexFillWithTable_" + System.currentTimeMillis() + ".xlsx";

    try (ExcelWriter excelWriter = EasyExcel.write(fileName)
            .withTemplate(templateFileName).build()) {
        WriteSheet writeSheet = EasyExcel.writerSheet().build();

        // 1. 写入 list 数据
        excelWriter.fill(data(), writeSheet);
        excelWriter.fill(data(), writeSheet);

        // 2. 写入 list 之前的变量
        Map<String, Object> map = new HashMap<>();
        map.put("date", "2019年10月9日13:28:28");
        excelWriter.fill(map, writeSheet);

        // 3. list 后面手动 write 统计行
        List<List<String>> totalListList = new ArrayList<>();
        List<String> totalList = new ArrayList<>();
        totalListList.add(totalList);
        totalList.add(null);
        totalList.add(null);
        totalList.add(null);
        totalList.add("统计:1000");
        excelWriter.write(totalListList, writeSheet);
    }
}
```

> **官方原文**:"这里是write 别和fill 搞错了"

### 场景 5:横向填充(多列横向并列)

```java
@Test
public void horizontalFill() {
    String templateFileName = "templates/horizontal.xlsx";
    String fileName = "output_horizontalFill_" + System.currentTimeMillis() + ".xlsx";

    try (ExcelWriter excelWriter = EasyExcel.write(fileName)
            .withTemplate(templateFileName).build()) {
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        // 横向:列方向展开
        FillConfig fillConfig = FillConfig.builder()
            .direction(WriteDirectionEnum.HORIZONTAL).build();
        excelWriter.fill(data(), fillConfig, writeSheet);
        excelWriter.fill(data(), fillConfig, writeSheet);

        Map<String, Object> map = new HashMap<>();
        map.put("date", "2019年10月9日13:28:28");
        excelWriter.fill(map, writeSheet);
    }
}
```

### 场景 6:多列表组合填充(同时填多个 list)

**模板**:包含 `{data1.}` `{data2.}` `{data3.}` 三种带前缀的列表

```java
@Test
public void compositeFill() {
    String templateFileName = "templates/composite.xlsx";
    String fileName = "output_compositeFill_" + System.currentTimeMillis() + ".xlsx";

    try (ExcelWriter excelWriter = EasyExcel.write(fileName)
            .withTemplate(templateFileName).build()) {
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        FillConfig fillConfig = FillConfig.builder()
            .direction(WriteDirectionEnum.HORIZONTAL).build();

        // ⚠️ 关键:多个 list 必须用 FillWrapper 包裹,prefix 必须与模板占位符前缀一致
        excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet);
        excelWriter.fill(new FillWrapper("data2", data()), writeSheet);  // data2 纵向
        excelWriter.fill(new FillWrapper("data3", data()), writeSheet);

        Map<String, Object> map = new HashMap<>();
        map.put("date", new Date());
        excelWriter.fill(map, writeSheet);
    }
}
```

> **官方原文**:"多个 list 必须用 `FillWrapper` 包裹;`{前缀.}` 前缀可以区分不同的 list;不同 list 可以指定不同方向(HORIZONTAL/VERTICAL)"

## Critical: Spring Boot 集成

### 1) 模板放在 `resources/templates/`
```java
@Service
public class SalaryFillService {

    public void fillSalary(String employeeId, OutputStream out) {
        InputStream templateIn = getClass()
            .getResourceAsStream("/templates/salary.xlsx");
        SalaryDTO data = salaryMapper.findByEmployee(employeeId);

        // 模板以流方式加载,避免临时文件
        try (ExcelWriter writer = EasyExcel.write(out)
                .withTemplate(templateIn).build()) {
            WriteSheet sheet = EasyExcel.writerSheet().build();
            writer.fill(data, sheet);
        }
    }
}
```

### 2) Controller 返回
```java
@GetMapping("/export/salary/{employeeId}")
public void exportSalary(@PathVariable String employeeId,
                         HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    String fileName = URLEncoder.encode("工资条", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

    salaryFillService.fillSalary(employeeId, response.getOutputStream());
}
```

### 3) 复杂场景:工资条批量打包
```java
public void batchExportSalary(List<String> employeeIds, OutputStream zipOut) {
    try (ZipOutputStream zos = new ZipOutputStream(zipOut);
         ExcelWriter writer = EasyExcel.write(/* unused */).build()) {  // 实际逐个写入临时文件
        for (String id : employeeIds) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            fillSalary(id, baos);
            ZipEntry entry = new ZipEntry("salary_" + id + ".xlsx");
            zos.putNextEntry(entry);
            zos.write(baos.toByteArray());
            zos.closeEntry();
        }
    }
}
```

## Critical: 业务模板设计规范

| 业务场景 | 推荐模板设计 | 关键代码 |
|---------|------------|---------|
| **工资条** | 单对象 `{name}` `{base}` `{bonus}` `{total}` | 简单填充 方案 1 |
| **员工合同** | 头部 `{company}` `{name}` `{id}` + 列表 `{items.}` | 简单 + 列表 |
| **对账单** | 头部 `{date}` `{account}` + 列表 `{details.}` + 底部"合计" | complexFillWithTable(先 fill 列表再 write 合计行) |
| **财务报表** | 横向多列 `{months.}` + 纵向 `{rows.}` | compositeFill(多个 FillWrapper) |
| **发票** | 单对象 + 函数 `=SUM()` | 简单填充(公式由模板预置) |
| **商品清单** | 列表 `{products.}` + 图片 | 列表 + 图片(图片需读为 byte[]) |
| **汇总表(按月)** | 头部 `{year}` + 列表 `{data1.}`(横向多列展开) | horizontalFill |

## Gotchas(常见错误)

| # | 错误 | 正确做法 |
|---|------|---------|
| 1 | Map 填充 list 漏掉某些 key | Map 填充时必须 put 所有 list 的 key(可 value=null) |
| 2 | 模板有 `{` `}` 字符未转义 | 用 `\{` `\}` 转义 |
| 3 | 复杂填充忘记 `forceNewRow=true` | list 下面还有内容时必须设置 |
| 4 | `forceNewRow=true` 用在百万级数据 | 改用 complexFillWithTable(list 放最后一行) |
| 5 | 03 版(`.xls`)做复杂填充 | 改用 07 版(`.xlsx`) |
| 6 | list 后追加合计行用 `fill` | 用 `write`("这里是write 别和fill 搞错了") |
| 7 | 横向多列忘了 `FillWrapper` | 多 list 必须用 `new FillWrapper("prefix", list)` |
| 8 | 横向多列方向忘了配 | 必须 `FillConfig.builder().direction(HORIZONTAL)` |
| 9 | 模板文件 `withTemplate(InputStream)` 用完未关 | 模板流是缓存的,try-with-resources 关闭 |
| 10 | 关闭 `ExcelWriter` 时漏掉 `finish()` | 用 try-with-resources 让 `ExcelWriter.close()` 触发 finish |
| 11 | 模板文件 > 几十 MB | 改用多个小模板分批填充 |
| 12 | 模板变量写错(大小写 / 漏 `{` `}`) | 模板变量名必须与 Map key / DTO 字段完全一致 |

## Key Classes / Methods

| 类型 | 名称 | 用途 |
|------|------|------|
| 入口 | `EasyExcel.write(path).withTemplate(template)` | 指定模板路径或流 |
| 入口 | `EasyExcel.write(OutputStream).withTemplate(stream)` | Web 输出流场景 |
| 核心 | `ExcelWriter` | 多次 fill 的容器,需 try-with-resources |
| 核心 | `WriteSheet` | 单个 sheet 配置 |
| 配置 | `FillConfig.builder().forceNewRow(true)` | 复杂填充(list 后还有内容) |
| 配置 | `FillConfig.builder().direction(HORIZONTAL)` | 横向填充 |
| 包装 | `FillWrapper("prefix", list)` | 多列表分组,前缀匹配模板 `{prefix.}` |
| 工具 | `EasyExcel.writerSheet().build()` | 创建默认 WriteSheet |
| 工具 | `EasyExcel.writerSheet(0, "sheet名").build()` | 指定 sheetNo 和名称 |

## References

- **官方文档**:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/fill
- **官方示例**:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java
- **项目主页**:https://easyexcel.opensource.alibaba.com/
- **GitHub**:https://github.com/alibaba/easyexcel
- **API 参考**:https://easyexcel.opensource.alibaba.com/docs/current/api/
- [references/api-reference.md](references/api-reference.md) — 完整 API 与注解索引
- [references/template-design.md](references/template-design.md) — 模板设计规范与真实业务模板
- [references/business-scenarios.md](references/business-scenarios.md) — 工资条/合同/对账单/发票等真实案例
- [examples/real-cases.md](examples/real-cases.md) — 6 种填充模式完整可复制代码
- [examples/spring-boot-integration.md](examples/spring-boot-integration.md) — Spring Boot 集成完整示例

More Backend Frameworks skills

← All Backend Frameworks skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY