跳转到内容

JQuick-Excel

纯 Java · Apache-2.0 · 基于 Apache POI

纯 Java 生成带图表、公式与样式的 Excel 报表不手写 Workbook,映射、公式、图表全在一份 XML 里

一段 EXPORT WITH DSL 声明列映射、金额格式、滚动期末库存公式、合计行样式与三维面积图;Java 侧构造业务数据、拿到强类型服务代理、调用一次方法,导出即完成 —— 底层由框架驱动 Apache POI,xls / xlsx 双格式、流式导出与 42 套主题开箱即用。

上面代码的真实导出结果 — JQuick-Excel
上面代码的真实导出结果

非设计稿:由示例代码运行导出,可下载 xlsx 与 XML 配置逐项对照

  • 0 行POI 样板代码
  • 10 种GRAPH 内置图表类型
  • 20 条内置数据校验规则
  • 42 套内置导出主题
7 行导出这份库存趋势报表的全部 Java 代码
List<JQuickRow> rows = inventoryRows(); // 业务数据:月份、期初、入库、出库
JQuickParseHandler parser =
new JQuickExcelExportXmlParseFactory("forestGreen", rows, out);
JQuickFactory factory = new JQuickXmlFactory(parser,
"jquick/biz/graph/0107_graph_inventory-trend.xml");
Graph0107InventoryTrendService service =
factory.createApi(Graph0107InventoryTrendService.class);
service.exportInventoryTrend("field", "value"); // 图表、公式、样式全在 XML 里

省略 import 与数据构造辅助方法;service 接口由 XML 的 namespace 生成代理,方法名即 XML 里的 excel name。

XML同一份输出的规则声明(XML DSL 节选)
EXPORT WITH SHEET="库存量趋势", HEADER=true,
MAPPING={"a":"月份","b":"期初","c":"入库","d":"出库","e":"期末"},
FORMAT={"b":"#,##0","c":"#,##0","d":"#,##0","e":"#,##0"},
FORMULAS={E2:'B2+C2-D2', E3:'E2+C3-D3', E4:'E3+C4-D4', E5:'E4+C5-D5',
C6:'SUM(C2:C5)', D6:'SUM(D2:D5)', E6:'E5'},
STYLE={ROW 6:{bold:true, fillPattern:solid_foreground,
fillForegroundColor:lightYellow, alignment:right}},
GRAPH={TYPE=AREA3D, TITLE="库存量趋势图", CATEGORY_AXIS=月份, VALUE_AXIS=库存,
CATEGORIES=["1月","2月","3月","4月"],
SERIES=[{NAME=期末, DATA=[4800,5200,4600,5100]}]}

映射、格式、逐行滚动余额公式、合计行高亮与折线图全部声明式表达;这段 DSL 就是上面截图的完整规则,可下载原始文件直接使用。

同样的库存趋势报表,手写 Apache POI 要写多少

Apache POI 是优秀的底层库,JQuick-Excel 正是基于它构建——下表比较的是「写法」,不是「能力」。

对比维度JQuick-Excel手写 Apache POI
上手方式EXPORT WITH DSL 一段声明完成整表手写 Workbook / Sheet / Row / Cell 逐层构建
字段映射与格式MAPPING + FORMAT 各一行逐列 setCellValue 并手动创建 DataFormat
公式与汇总FORMULAS 声明逐行公式与 SUM 合计逐单元格 setCellFormula 并自行维护行号
图表GRAPH 一段 DSL,10 种图表类型XDDF 手工构建坐标轴、数据引用与系列
数据校验20 条内置规则,作用域到行 / 列 / 单元格自行实现校验逻辑与错误提示
主题样式42 套内置导出主题,按名引用手写 CellStyle 或自建模板文件
大文件流式导出与批量消费内置自行切换 SXSSF 并管理滑动窗口与样式缓存

底层就是 Apache POI:JQuick-Excel 把重复样板收进一份 XML,POI 的能力一个不少,两者同为 Apache-2.0,可放心共存。

JQuick-Excel 是一个轻量级 Java 框架,用于导入与导出 xls 与 xlsx 工作簿。它将 XML 服务定义与声明式 DSL 结合,统一表达字段映射、类型转换、数据校验、公式、样式、合并汇总、图表与页脚。

XML 定义使 Excel 规则与服务契约保持一致:Java 调用方根据该定义创建服务代理,再向导入或导出操作传入业务数据、流与上下文值。框架不强制依赖 Spring,普通 Java 项目与 Spring Boot 项目使用同一套 API;底层基于 Apache POI,运行环境为纯 Java,可在 Windows、Linux、macOS 及信创操作系统上使用。

  • 双格式支持:同时支持 xls 与 xlsx 工作簿。
  • 声明式规则:通过 IMPORT WITH 与 EXPORT WITH 声明导入导出规则。
  • 字段映射与转换:字段映射、显示格式化与基于表达式的逐行值转换。
  • 20 条内置校验规则,支持行、列、单元格与矩形范围的明确作用域。
  • 公式能力:数学、日期时间、文本、逻辑与查找公式,按单元格、行、列或范围写入。
  • 10 种图表类型,通过 GRAPH DSL 配置。
  • 样式设置:行、列、单元格与区域样式,含字体、对齐、边框、填充。
  • 9 种合并聚合策略:最大值、最小值、平均值、求和、首值、末值、拼接、计数与固定值。
  • 42 套内置导出主题。
  • 大文件友好:流式(SXSSF)导出、大文件导入、批量消费与单元格样式缓存。
  • 将业务系统中的订单、用户、报表等数据导出为 Excel 文件供下载与归档。
  • 从第三方或运营人员提供的 Excel 文件中导入数据,完成表头映射、值转换与合法性校验。
  • 需要在导出结果中写入汇总公式、合并单元格、图表与页脚的统计报表场景。
  • 高行数数据的导入导出,需要流式写入与分批读取以控制内存占用。

环境要求:

项目 要求
JDK Java 8 或更高版本
工作簿格式 xls、xlsx
底层依赖 Apache POI、内置词法与语法分析运行时、Javelin Core、JQuick XML 代理,均为纯 Java
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-excel</artifactId>
<version>3.6.0</version>
</dependency>

版本号请以 Maven Central 上的最新发布版为准。

implementation 'io.github.paohaijiao:jquick-excel:3.6.0'

使用 JQuick-Excel 完成一次导入需要三个部分:类路径中的 XML 定义、服务接口、Java 调用代码。

第一步,在 resources 目录创建 jquick-excel.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StudentService">
<excel name="importExcel" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"ID":"id","Name":"name","Age":"age"}
]]>
</excel>
</excels>

第二步,定义与 XML namespace 同名的服务接口:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface StudentService {
List<JQuickRow> importExcel(@Param("field") String field, @Param("value") String value);
}

第三步,创建代理并调用:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class SimpleImportDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = SimpleImportDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StudentService service = factory.createApi(StudentService.class);
List<JQuickRow> rows = service.importExcel("field", "value");
System.out.println("导入行数:" + rows.size());
}
}
}

在 jquick-excel.xml 中增加导出定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StudentService">
<excel name="exportExcel" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"id":"ID","name":"Name","age":"Age"}
]]>
</excel>
</excels>

服务接口与 Java 调用代码:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface StudentService {
void exportExcel(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class SimpleExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> student = new LinkedHashMap<>();
student.put("id", "1001");
student.put("name", "Alice");
student.put("age", 20);
data.add(student);
// 将 Map 数据转换为框架行对象
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StudentService service = factory.createApi(StudentService.class);
service.exportExcel("field", "value");
System.out.println("导出完成");
}
}
}
  • 导出:业务数据中的性别码值(1=男、2=女)通过 JContext 字典转换为 Excel 中可读的「男 / 女」,出生日期统一格式化为 yyyy-MM-dd,表头加粗着色,并在年龄列末尾追加 SUM 汇总公式。
  • 导入:读取运营同学提供的测试文件,按表头映射为 JQuickRow 字段,性别文字反向翻译为内部码值,出生日期同样格式化为 yyyy-MM-dd。
  • 导入与导出共用一份 jquick-excel.xml,但两者 MAPPING 方向相反:导出是「字段 → 表头」,导入是「表头 → 字段」。

Demo 的读写路径与截图位置固定如下,代码中严格使用这些路径:

用途 Windows 本地路径 说明
导入测试文件目录 D:\idea\jquick-docs\public\jquick-excel\import 测试文件手工放入此目录,程序只读取
导出文件输出目录 D:\idea\jquick-docs\public\jquick-excel\export 程序运行后自动生成导出文件,无需手工创建
运行截图目录 D:\idea\jquick-docs\public\jquick-excel\image Demo 截图统一存放于此,文档以 /jquick-excel/image/文件名 引用
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-excel</artifactId>
<version>3.6.0</version>
</dependency>

将测试文件 students.xlsx 放到 D:\idea\jquick-docs\public\jquick-excel\import 目录。文件内只有一张工作表 Sheet1,第一行为表头,内容如下(也可直接下载站点已放置好的文件:students.xlsx):

学号 姓名 性别 年龄 出生日期 班级
2024001 张三 男 20 2024-09-01 计算机1班
2024002 李四 女 21 2024-09-01 计算机2班
2024003 王五 男 22 2024-09-02 计算机3班

在类路径(src/main/resources)下创建 jquick-excel.xml。导出规则负责表头命名、字典转换、日期格式、表头样式与年龄汇总公式;导入规则负责反向映射:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StudentExcelService">
<!-- 导出:Java 字段 -> Excel 表头,性别码值经字典转换,日期统一为 yyyy-MM-dd -->
<excel name="exportStudents" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="学生信息",
HEADER=true,
MAPPING={
"id":"学号",
"name":"姓名",
"gender":"性别",
"age":"年龄",
"enrollmentDate":"出生日期",
"className":"班级"
},
TRANSFORM={
"gender":trans(${dict},${gender}),
"enrollmentDate":dateFormat(${enrollmentDate},'yyyy-MM-dd')
},
FORMULAS={
D5:'SUM(D2:D4)'
},
STYLE={
ROW 1: {
fontName: Arial,
fontHeightInPoints: 12,
bold: true,
color: white,
fillPattern: solid_foreground,
fillForegroundColor: darkBlue,
alignment: center
}
}
]]>
</excel>
<!-- 导入:Excel 表头 -> JQuickRow 字段,性别文字经字典还原为码值 -->
<excel name="importStudents" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
MAPPING={
"学号": "id",
"姓名": "name",
"性别": "gender",
"年龄": "age",
"出生日期": "birthday",
"班级": "className"
},
TRANSFORM={
"gender":trans(${dict},${gender}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
}
]]>
</excel>
</excels>

接口全限定名与 XML 的 namespace 完全一致,方法名与 excel 节点的 name 完全一致:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
/**
* 学生信息导入导出服务契约。
*/
public interface StudentExcelService {
/** 导出学生信息到 Excel,导出方法返回 void。 */
void exportStudents(@Param("field") String field, @Param("value") String value);
/** 从 Excel 导入学生信息,返回转换后的结构化行数据。 */
List<JQuickRow> importStudents(@Param("field") String field, @Param("value") String value);
}

第四步:准备业务对象与保序行对象

Section titled “第四步:准备业务对象与保序行对象”

导出列顺序由首行数据的字段遍历顺序决定。先定义学生业务对象:

package com.example;
import java.util.Date;
/**
* 学生领域对象。
*/
public class Student {
private String id;
private String name;
/** 性别码值:1=男,2=女,导出时经字典转换为文字。 */
private Integer gender;
private Integer age;
private Date enrollmentDate;
private String className;
public Student() {
}
public Student(String id, String name, Integer gender, Integer age,
Date enrollmentDate, String className) {
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
this.enrollmentDate = enrollmentDate;
this.className = className;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getEnrollmentDate() {
return enrollmentDate;
}
public void setEnrollmentDate(Date enrollmentDate) {
this.enrollmentDate = enrollmentDate;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}

JQuickRow 默认基于 HashMap 存储字段,列顺序不确定。为了让导出列固定为「学号 → 姓名 → 性别 → 年龄 → 出生日期 → 班级」(年龄落在 D 列,D5 公式才能正确求和),Demo 使用一个仅覆盖遍历顺序的保序子类,取值与转换逻辑仍由框架处理:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 保序行对象:按字段 put 的顺序暴露 keySet / entrySet。
* JQuick-Excel 导出时以首行数据的 keySet 顺序决定 Excel 列顺序。
*/
public class OrderedJQuickRow extends JQuickRow {
private static final long serialVersionUID = 1L;
/** 按首次 put 的顺序记录字段名。 */
private final List<String> keys = new ArrayList<>();
@Override
public Object put(String key, Object value) {
if (!containsKey(key)) {
keys.add(key);
}
return super.put(key, value);
}
@Override
public Set<String> keySet() {
return new LinkedHashSet<>(keys);
}
@Override
public Set<Map.Entry<String, Object>> entrySet() {
Set<Map.Entry<String, Object>> ordered = new LinkedHashSet<>();
for (String key : keys) {
ordered.add(new EntryView(key));
}
return ordered;
}
/** 与保序键集合配套的只读条目视图。 */
private final class EntryView implements Map.Entry<String, Object> {
private final String key;
private EntryView(String key) {
this.key = key;
}
@Override
public String getKey() {
return key;
}
@Override
public Object getValue() {
return get(key);
}
@Override
public Object setValue(Object value) {
Object old = get(key);
put(key, value);
return old;
}
}
}

主程序严格按照目录约定读写:从 import 读取测试文件,导出结果自动输出到 export。

package com.example;
import com.github.paohaijiao.param.JContext;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* JQuick-Excel 导入导出完整 Demo。
*
* 目录约定:
* 1. 导入测试文件:D:/idea/jquick-docs/public/jquick-excel/import/students.xlsx
* 2. 导出文件输出:D:/idea/jquick-docs/public/jquick-excel/export/students-export.xlsx
* 3. 运行截图存放:D:/idea/jquick-docs/public/jquick-excel/image
*/
public class StudentImportExportDemo {
/** 导入测试文件读取目录(测试文件手工放入此目录)。 */
private static final String IMPORT_DIR = "D:/idea/jquick-docs/public/jquick-excel/import";
/** 导出文件输出目录(程序自动生成,无需手工创建文件)。 */
private static final String EXPORT_DIR = "D:/idea/jquick-docs/public/jquick-excel/export";
public static void main(String[] args) throws Exception {
// 第一步:把业务数据导出为 Excel,文件自动输出到 export 目录
exportStudents();
// 第二步:读取 import 目录中的测试 Excel,完成表头映射与字典转换
importStudents();
}
/**
* 导出示例:业务数据 -> JQuickRow -> XML 代理 -> export 目录下的 xlsx 文件。
*/
private static void exportStudents() throws Exception {
// 1. 准备导出用的性别字典:库中码值 -> Excel 中展示的文字
Map<String, Object> genderDict = new HashMap<>();
genderDict.put("1", "男");
genderDict.put("2", "女");
JContext context = new JContext();
context.put("dict", genderDict);
// 2. 准备业务数据(实际项目中通常来自数据库查询)
List<Student> data = new ArrayList<>();
data.add(new Student("2024001", "张三", 1, 20, date(2024, 9, 1), "计算机1班"));
data.add(new Student("2024002", "李四", 2, 21, date(2024, 9, 1), "计算机2班"));
data.add(new Student("2024003", "王五", 1, 22, date(2024, 9, 2), "计算机3班"));
// 3. 按固定列顺序组装行对象:A 学号 | B 姓名 | C 性别 | D 年龄 | E 出生日期 | F 班级
List<JQuickRow> rows = new ArrayList<>();
for (Student student : data) {
JQuickRow row = new OrderedJQuickRow();
row.put("id", student.getId());
row.put("name", student.getName());
row.put("gender", student.getGender());
row.put("age", student.getAge());
row.put("enrollmentDate", student.getEnrollmentDate());
row.put("className", student.getClassName());
rows.add(row);
}
// 4. 导出文件固定输出到 export 目录
String exportFile = EXPORT_DIR + "/students-export.xlsx";
try (OutputStream output = new FileOutputStream(exportFile)) {
// 解析器携带上下文(字典)、数据行与输出流
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(context, rows, output);
// 按类路径资源名加载 XML 规则定义
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StudentExcelService service = factory.createApi(StudentExcelService.class);
// 方法名与 XML 中的 excel 节点 name="exportStudents" 对应
service.exportStudents("field", "value");
}
System.out.println("导出完成,文件位置:" + exportFile);
}
/**
* 导入示例:读取 import 目录中的测试文件,映射为 JQuickRow 列表并打印。
*/
private static void importStudents() throws Exception {
// 1. 准备导入用的性别字典:Excel 中展示的文字 -> 系统内部码值(方向与导出相反)
Map<String, Object> genderDict = new HashMap<>();
genderDict.put("男", "1");
genderDict.put("女", "2");
JContext context = new JContext();
context.put("dict", genderDict);
// 2. 测试文件固定从 import 目录读取
String importFile = IMPORT_DIR + "/students.xlsx";
try (InputStream input = new FileInputStream(importFile)) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(context, input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StudentExcelService service = factory.createApi(StudentExcelService.class);
// 3. 执行导入,返回按 MAPPING 右侧字段命名的结构化行
List<JQuickRow> rows = service.importStudents("field", "value");
System.out.println("导入完成,读取行数:" + rows.size());
for (JQuickRow row : rows) {
// 性别文字已被字典转换为码值;数字单元格读入为 Double,
// 整数场景使用 getIntConverted 做安全转换。
System.out.println("学号=" + row.getString("id")
+ ",姓名=" + row.getString("name")
+ ",性别码值=" + row.getString("gender")
+ ",年龄=" + row.getIntConverted("age")
+ ",出生日期=" + row.getString("birthday")
+ ",班级=" + row.getString("className"));
}
}
}
private static Date date(int year, int month, int day) {
Calendar calendar = Calendar.getInstance();
calendar.set(year, month - 1, day, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
}

在 IDE 中运行 StudentImportExportDemo,控制台输出如下(已省略框架启动 Banner 与日志):

导出完成,文件位置:D:/idea/jquick-docs/public/jquick-excel/export/students-export.xlsx
导入完成,读取行数:3
学号=2024001,姓名=张三,性别码值=1,年龄=20,出生日期=2024-09-01,班级=计算机1班
学号=2024002,姓名=李四,性别码值=2,年龄=21,出生日期=2024-09-01,班级=计算机2班
学号=2024003,姓名=王五,性别码值=1,年龄=22,出生日期=2024-09-02,班级=计算机3班

导出文件 students-export.xlsx 自动出现在 export 目录,工作表「学生信息」内容如下:

行 A 学号 B 姓名 C 性别 D 年龄 E 出生日期 F 班级
1(表头) 学号 姓名 性别 年龄 出生日期 班级
2 2024001 张三 男 20 2024-09-01 计算机1班
3 2024002 李四 女 21 2024-09-01 计算机2班
4 2024003 王五 男 22 2024-09-02 计算机3班
5 SUM(D2:D4)

截图统一保存到 D:\idea\jquick-docs\public\jquick-excel\image,在文档中通过站点路径 /jquick-excel/image/... 引用。需要补充截图时,按下表文件名放入对应图片即可,无需修改文档链接。

截图内容 本地存放位置 文档引用路径
import 目录中的测试文件在 Excel/WPS 中的打开效果 public/jquick-excel/image/demo-import-file.png /jquick-excel/image/demo-import-file.png
程序运行后 export 目录生成的导出文件效果(含表头样式与 D5 汇总结果) public/jquick-excel/image/demo-export-file.png /jquick-excel/image/demo-export-file.png
IDE 控制台导入导出运行结果 public/jquick-excel/image/demo-console.png /jquick-excel/image/demo-console.png

1)导入测试文件效果(import/students.xlsx,截图位置:image/demo-import-file.png)

导入测试文件 students.xlsx 效果

2)导出文件效果(export/students-export.xlsx,截图位置:image/demo-export-file.png)

导出文件 students-export.xlsx 效果

3)控制台运行结果(截图位置:image/demo-console.png)

IDE 控制台运行结果

  • Java 中路径使用正斜杠(D:/idea/...)即可;请先确认 import、export、image 三个目录已存在。
  • 输入流与输出流由调用方负责关闭,示例统一使用 try-with-resources;代理调用期间流必须保持打开。
  • 导出字典(1→男)与导入字典(男→1)方向相反,两个 JContext 不能混用。
  • 若不要求固定列顺序,可直接使用 JQuickRow.toRows(JObjectConverter.convert(data));需要列顺序稳定时再使用本示例的保序行对象。
  • 导入读取到的数字单元格为 Double 类型,使用 getIntConverted("age") 等转换方法取值,避免直接 getString 产生类型转换异常。

导入 DSL 的固定入口为 IMPORT WITH,各参数之间以英文逗号分隔:

IMPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"ID":"id"},
TRANSFORM={"name":toUpper(${name})},
VALIDATION={C2:C4:{integer{required:true}}}
  • 作用:选择要读取的源工作表。
  • 取值说明:
    • 字符串:工作表名称,写法 SHEET="Students"。
    • 整数:工作表位置,从 1 开始计数,写法 SHEET=1。
    • 省略该参数时,默认读取工作簿中的第一张工作表。
  • 示例:
IMPORT WITH SHEET="Students", HEADER=true
  • 作用:声明第一行是否为表头行。
  • 取值说明:布尔值,HEADER=true 表示首行为表头,数据从表头下一行开始读取。
  • 默认行为:未显式声明时按启用表头处理。
  • 示例:
IMPORT WITH SHEET="Students", HEADER=true
  • 作用:将工作表中的表头文本映射为结果行中的字段名。
  • 取值说明:键为 Excel 表头文本,值为目标字段名,整体为 JSON 风格的映射结构。
  • 方向:{"表头":"字段"},例如 {"ID":"id"} 表示表头为 ID 的列,读取后以 id 作为字段名。
  • 示例:
IMPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"ID":"id","Name":"name","Age":"age"}
  • 作用:对读取到的每一行的指定字段应用转换表达式,再写入结果行。
  • 取值说明:键为目标字段名,值为转换表达式。
  • 变量语法:
    • ${field}:读取当前行中指定字段的值,例如 ${name}。
    • ${key}:读取 JContext 中以该键保存的外部值,例如 ${dict}。
  • 内置转换函数:
    • toUpper(${name}):将文本转换为大写。
    • dateFormat(${birthday},'yyyy-MM-dd'):按指定模式格式化日期值。
    • trans(${dict},${sex}):使用 JContext 中的字典对值进行映射。
  • 示例:
IMPORT WITH
SHEET="Sheet1",
HEADER=true,
MAPPING={"学号":"no","姓名":"name","性别":"sex","出生日期":"birthday"},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
}

JContext 字典的构造方式:

import com.github.paohaijiao.param.JContext;
import java.util.HashMap;
import java.util.Map;
public class ContextDemo {
public static JContext buildContext() {
Map<String, Object> gender = new HashMap<>();
gender.put("男", "1");
gender.put("女", "2");
JContext context = new JContext();
context.put("dict", gender);
return context;
}
}

外部依赖 jquick-transform-function 还通过 Java SPI 提供 formatDate、translate 等更多函数;只有显式引入该依赖后,这些函数才可以在 TRANSFORM 中使用。

  • 作用:在消费导入结果之前,对指定范围应用数据校验规则。
  • 取值说明:以「范围 + 规则块」的形式声明,规则块内可配置 required、msg 与 map:
    • required:是否要求必填,布尔值。
    • msg:校验不通过时使用的自定义提示信息;未提供时使用规则默认信息。
    • map:规则自身所需的参数集合,如上下限、格式模式等。
  • 校验范围语法:
范围 含义 示例
行 单行或行区间 ROW 5、ROW 1..10
列 单列或列区间 COL A、COL A..D
单元格 单个单元格 C1
矩形区域 矩形单元格区域 A1:B5
  • 内置校验规则(共 20 条):
规则 作用 map 常用参数
boolean 校验布尔值 ——
date_format 校验日期字符串格式 format
max_date 校验日期上限 format、maxDate
min_date 校验日期下限 format、minDate
integer 校验整数 ——
decimal 校验小数 ——
max_value 校验数值上限 maxValue
min_value 校验数值下限 minValue
dict 校验字典成员关系 字典键值对
email 校验邮箱地址 ——
mobile 校验手机号码 ——
max_length 校验字符串最大长度 maxLength
min_length 校验字符串最小长度 minLength
regex 校验正则表达式 pattern
start_with 要求指定前缀 startWith
not_start_with 禁止指定前缀 notStartWith
end_with 要求指定后缀 endWith
not_end_with 禁止指定后缀 notEndWith
contain 要求包含子串 contains
not_contain 禁止包含子串 notContain
  • 示例:
IMPORT WITH VALIDATION={
ROW 2..100:{
integer{required:true,msg:'年龄必须是整数'}
},
C2:C100:{
email{required:true,msg:'邮箱格式无效'}
}
}
  • 校验不通过的行为:校验不通过时抛出异常,异常信息为配置的 msg,未配置 msg 时使用对应规则的默认提示信息。
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
MAPPING={
"学号": "no",
"姓名": "name",
"性别": "sex",
"年龄": "age",
"出生日期": "birthday"
},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
},
VALIDATION={
ROW 2..100:{
integer{required:true,msg:'年龄必须是整数'}
}
}

导出 DSL 的固定入口为 EXPORT WITH,各参数之间以英文逗号分隔:

EXPORT WITH
SHEET="Report",
HEADER=true,
MAPPING={"id":"ID","name":"Name","amount":"Amount"},
FORMAT={"amount":"currency"},
TRANSFORM={"name":toUpper(${name})},
FORMULAS={D2:'SUM(B2:C2)'},
STYLE={ROW 1:{bold:true}},
MERGE:{ROWS 1..1,COLS A..D WITH FIRST},
GRAPH={TYPE=PIE,TITLE="Revenue",CATEGORIES=["Q1"],SERIES=[{NAME="2023",DATA=[1]}]},
FOOTER="Generated by JQuickExcel"
  • 作用:选择导出数据写入的目标工作表。
  • 取值说明:字符串为工作表名称;整数位置从 1 开始计数;省略时使用第一张工作表。
  • 行为说明:按名称指定的工作表不存在时,会以该名称新建工作表。
  • 示例:
EXPORT WITH SHEET="Report", HEADER=true
  • 作用:控制是否根据映射自动生成表头行。
  • 取值说明:布尔值,HEADER=true 表示生成表头。
  • 默认行为:未显式声明时按生成表头处理。
  • 示例:
EXPORT WITH SHEET="Report", HEADER=true
  • 作用:为指定字段对应的数据单元格叠加 Excel 数字显示格式(日期、千分位、百分比、货币等)。
  • 语法:FORMAT={"字段名":"格式码"},可同时声明多个字段,逗号分隔。
  • 取值说明:
位置 取值说明 必填
键(字段名) 源数据字段名,区分大小写,与 MAPPING 键及 JQuickRow 中的键保持一致 是
值(格式码) 标准 Excel 数字格式码,原样传入 POI DataFormat.getFormat();空字符串等于不设置格式 是

常用格式码如下:

配置项名称 描述 用法
yyyy-MM-dd 日期,年-月-日 FORMAT={"enrollmentDate":"yyyy-MM-dd"}
yyyy"年"m"月"d"日" 中文日期 FORMAT={"enrollmentDate":"yyyy\"年\"m\"月\"d\"日\""}
yyyy-MM-dd HH:mm 日期 + 时分(24 小时制) FORMAT={"createdAt":"yyyy-MM-dd HH:mm"}
#,##0 整数,带千分位分隔符 FORMAT={"quantity":"#,##0"}
#,##0.00 数值,千分位且固定两位小数 FORMAT={"amount":"#,##0.00"}
0.00 数值,固定两位小数,不带千分位 FORMAT={"score":"0.00"}
0.00% 百分比(底层数值乘 100 显示) FORMAT={"rate":"0.00%"}
¥#,##0.00 人民币货币格式 FORMAT={"amount":"¥#,##0.00"}
"$"#,##0.00 美元货币格式 FORMAT={"amount":"\"$\"#,##0.00"}
000000 数字补零(如 6 位工号、邮编) FORMAT={"empNo":"000000"}
  • 示例:
EXPORT WITH
FORMAT={"amount":"¥#,##0.00","enrollmentDate":"yyyy-MM-dd"}
  • 作用:将源数据字段映射为导出工作表的表头文本。
  • 语法:MAPPING={"源字段":"表头文本"},方向为「字段 → 表头」,可一次声明多个。
  • 取值说明:
配置项名称 描述 用法
"源字段":"表头文本" 将该列的表头重命名为指定文本 MAPPING={"id":"主键","name":"姓名"}
未在 MAPPING 中声明的字段 该列仍会导出,表头默认直接使用字段名本身 MAPPING={"id":"主键"}(其余列沿用字段名)
  • 示例:
EXPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"id":"ID","name":"Name","age":"Age"}
  • 作用:写入单元格之前,对每行指定字段计算转换表达式,用计算结果替换原值。
  • 语法:TRANSFORM={"字段名":函数调用},可一次声明多个字段。
  • 参数来源:${field} 读取当前行字段值,${key} 读取 JContext 中预置的值。
  • 查找顺序:先按函数名在 SPI 注册表(JQuickMethodInvocationManager)中查找提供者;未命中时回退到内置求值器。

内置求值器函数:

配置项名称 描述 用法
toUpper 将文本转为大写 TRANSFORM={"name":toUpper(${name})}
dateFormat 按模式格式化日期值 TRANSFORM={"enrollmentDate":dateFormat(${enrollmentDate},'yyyy-MM-dd')}
trans 通过 JContext 中的字典映射值 TRANSFORM={"gender":trans(${dict},${gender})}

SPI 扩展函数(jquick-transform-function)

Section titled “SPI 扩展函数(jquick-transform-function)”

除内置函数外,独立模块 jquick-transform-function 通过 Java SPI 提供 248 个函数(如 formatDate、translate、maskEmail、idCardValidate 等)。各函数的参数、返回值与完整用法请以该仓库文档为准,函数扩展统一遵从 SPI 契约:

<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-transform-function</artifactId>
<version>1.4.0</version>
</dependency>

SPI 契约(com.github.paohaijiao.function.core.JQuickMethodFunctionProvider):

配置项名称 描述 用法
getMethodName() 声明函数名,TRANSFORM 按此名称查找 返回 "formatDate"
invoke(List<Object> args) 接收求值后的参数并返回结果 return args.get(0);
getPriority() 加载优先级,默认 5000,数字小者优先 default int getPriority()
META-INF/services/...JQuickMethodFunctionProvider SPI 服务描述文件,逐行列出实现类全限定名 com.example.FullNameFunction

函数分组目录(完整 248 个函数的清单与用法以 jquick-transform-function 仓库为准):

配置项名称 描述 代表用法
array 数组与列表判断 isArray(value)
bit 位运算 bitAnd(a,b)、bitOr(a,b)、bitXor(a,b)
bool 布尔判断 isBoolean(value)
business 银行卡、邮箱、性别、身份证、手机号处理与校验 bankCardMask(cardNo)、idCardValidate(idCard)、phoneMask(phone)、isEmail(value)
collection 空值判断、集合拼接与长度 isEmpty(value)、join(list,delimiter)、size(value)
condition 比较、范围、分支与空值默认 between(value,min,max)、caseWhen(...)、coalesce(...)、nvl(value,defaultValue)
convert 布尔、日期、short 类型转换 toBoolean(value)、toDate(value,pattern)
date 日期运算、提取、比较、格式化 addDays(date,days)、daysBetween(d1,d2)、formatDate(date,pattern)、now()、today()
geometry 几何、向量、矩阵、复数运算 distance(x1,y1,x2,y2)、gcd(a,b)、factorial(n)、isPrime(n)
extra 类型转换、数字格式化、类型检查 cast(value,targetClass)、formatNumber(number,pattern)、typeOf(value)
json JSON 序列化 toJson(value)
math 四则运算、三角函数、统计、进制转换 abs(value)、roundTo(value,places)、sum(...)、toHex(number)
random 随机值、随机选择、UUID randomInt(min,max)、randomChoice(list)、randomUUID()、shuffle(list)
string 字符串查找、替换、大小写、脱敏、编码 substring(str,begin,end)、replaceAll(str,regex,rep)、maskEmail(email)、base64Encode(str)
translate 上下文字典翻译 translate(context,code,dictType,defaultValue)
  • 示例:
EXPORT WITH
MAPPING={"name":"Name","gender":"Gender"},
TRANSFORM={
"name":toUpper(${name}),
"gender":trans(${dict},${gender})
}
  • 作用:向指定单元格、行或列写入 Excel 原生公式,公式由表格软件(Excel/WPS)打开时计算。
  • 语法:FORMULAS={目标:'公式文本'},可同时声明多个目标,逗号分隔;公式文本必须加引号(单双引号均可)。
  • 运行行为:框架通过 POI Cell.setCellFormula(...) 写入公式;未能识别的函数按自定义公式原样写入。

公式目标写法:

配置项名称 描述 用法
单元格 C1 向单个单元格写入公式 FORMULAS={D5:'SUM(D2:D4)'}
行目标 ROW 5 向指定行的所有已用列写入同一公式(相对引用按列平移) FORMULAS={ROW 5:'SUM(B2:C2)'}
行区间 ROW 1..10 向行区间内每一行的所有已用列写入公式 FORMULAS={ROW 2..10:'SUM(B2:C2)'}
列目标 COL A 向指定列写入公式 FORMULAS={COL E:'D2*0.1'}
列区间 COL A..D 向列区间写入公式 FORMULAS={COL E..F:'D2*0.1'}

内置公式函数(按分类完整列出):

数学函数

配置项名称 描述 用法
ABS 绝对值 ABS(D2)
AVERAGE 算术平均值 AVERAGE(D2:D4)
COUNT 统计数值单元格数量 COUNT(D2:D4)
MAX 最大值 MAX(D2:D4)
MIN 最小值 MIN(D2:D4)
POWER 幂运算 POWER(2,3)
RAND 返回 [0,1) 随机值 RAND()
RANK 数值在区域内的排名 RANK(D2,D2:D4)
ROUND 按位数四舍五入 ROUND(3.14159,2)
SQRT 平方根 SQRT(4)
STDEV 样本标准差 STDEV(D2:D4)
SUM 数值求和 SUM(D2:D4)

日期时间函数

配置项名称 描述 用法
DATE 组合年月日生成日期(与 TIME 组合时按日期时间处理) DATE(2023,5,15)
DATETIME 创建日期时间值 DATETIME(2023,5,15,14,30,0)
DAY 提取日期中的日 DAY(A1)
DAYS 计算两个日期相差天数 DAYS(A1,A2)
EDATE 日期按月偏移 EDATE(A1,3)
EOMONTH 返回偏移若干月后的月末日期 EOMONTH(A1,3)
HOUR 提取小时 HOUR(A1)
MINUTE 提取分钟 MINUTE(A1)
MONTH 提取月份 MONTH(A1)
NETWORKDAYS 统计两个日期间的工作日数 NETWORKDAYS(A1,A2)
NOW 当前日期时间 NOW()
SECOND 提取秒 SECOND(A1)
TIME 组合时分秒生成时间 TIME(14,30,0)
TODAY 当前日期 TODAY()
WEEKDAY 返回星期编号 WEEKDAY(A1,2)
WEEKNUM 返回一年中的周数 WEEKNUM(A1,1)
WORKDAY 按工作日偏移日期 WORKDAY(A1,3,A2)
YEAR 提取年份 YEAR(A1)

文本函数

配置项名称 描述 用法
CONCAT 拼接多个文本 CONCAT(A1,B1)
CONCATENATE 拼接多个值(文本/数字/布尔) CONCATENATE("A",1,TRUE)
EXACT 区分大小写比较两个文本 EXACT("a","A")
FIND 区分大小写查找文本位置 FIND("n",A1)
LEFT 从左侧取指定长度文本 LEFT(A1,2)
LEN 文本字符数 LEN(A1)
LOWER 转为小写 LOWER(A1)
MID 按起始位置和长度取文本 MID(A1,2,3)
PROPER 每个单词首字母大写 PROPER(A1)
REPLACE 按位置替换文本 REPLACE(A1,2,2,"XY")
RIGHT 从右侧取指定长度文本 RIGHT(A1,2)
SEARCH 不区分大小写查找文本位置 SEARCH("n",A1,3)
SUBSTITUTE 按内容替换文本 SUBSTITUTE(A1,"a","b")
TEXT 按格式码将值转为文本 TEXT(C2,"0.0%")
TRIM 去除两端空格 TRIM(A1)
UPPER 转为大写 UPPER(A1)
VALUE 文本转数字 VALUE(A1)

逻辑与查询函数

配置项名称 描述 用法
IF 条件成立返回一个值,否则返回另一个值 IF(D2>0,"Yes","No")
AND 所有条件均成立 AND(A1>0,B1>0)
OR 任一条件成立 OR(A1>0,B1>0)
LOOKUP 向量查询(在单行/列中查找并返回另一单行/列对应值) LOOKUP(22,D2:D4,C2:C4)
  • 示例:
EXPORT WITH FORMULAS={
D5:'SUM(D2:D4)',
E5:'ROUND(AVERAGE(D2:D4),2)',
F5:'IF(D2>0,RANK(D2,D2:D4),0)'
}
  • 作用:对行、列、单元格或区域应用样式。
  • 取值说明:键为样式目标,值为样式属性集合。
  • 目标语法:ROW 1、COL A、单元格 A2、区域 A2:C100。
  • 样式属性:
属性 作用 示例值
fontName 字体族 Arial
fontHeightInPoints 字号(磅) 12
bold 粗体 true
italic 斜体 true
color 字体颜色 yellow
alignment 水平对齐 center
verticalAlignment 垂直对齐 center
wrapText 自动换行 true
borderLeft / borderRight / borderTop / borderBottom 四周边框 thin
fillPattern 填充模式 solid_foreground
fillForegroundColor 前景填充色 yellow
fillBackgroundColor 背景填充色 white
dataFormatString 单元格显示格式 "yyyy-MM-dd"

边框取值:none、thin、medium、dashed、dotted、thick、double。

填充模式取值:no_fill、solid_foreground、fine_dots、sparse_dots、thin_horz_bands、thin_vert_bands。

  • 示例:
EXPORT WITH STYLE={
ROW 1:{
fontName:Arial,
fontHeightInPoints:12,
italic:true,
color:yellow,
bold:true
},
A2:C100:{
alignment:center,
wrapText:true,
borderBottom:thin,
fillForegroundColor:lightYellow
}
}
  • 作用:定义单元格合并区域,并在 WITH 后指定合并后保留值的聚合策略。
  • 取值说明:以行区间与列区间描述合并范围。
  • 聚合策略(9 种):
策略 含义
MAX 使用最大值
MIN 使用最小值
AVG 使用平均值
SUM 对值求和
FIRST 使用第一个值
LAST 使用最后一个值
CONCAT 拼接值
COUNT 统计值数量
VALUE 使用固定值
  • 示例:
EXPORT WITH MERGE:{
ROWS 1..1,
COLS A..F WITH FIRST
}
  • 作用:在导出工作表中插入 Excel 图表,并以声明式结构描述图表类型、标题、坐标轴、分类与数据系列。
  • 入口语法:GRAPH = { 配置项, 配置项, ... },各配置项逗号分隔。

完整配置项如下:

配置项名称 描述 用法
TYPE 图表类型,取图表类型枚举值之一 TYPE = LINE
TITLE 图表标题,字符串必须加引号 TITLE = "月度销售统计"
CATEGORY_AXIS 分类轴(横轴)标题,取不加引号的标识符,支持中文 CATEGORY_AXIS = 月份
VALUE_AXIS 数值轴(纵轴)标题,取不加引号的标识符,支持中文 VALUE_AXIS = 销售额
CATEGORIES 分类值数组,逐项对应数据点;文本需加引号 CATEGORIES = ["1月", "2月", "3月"]
SERIES 数据系列数组;每个系列对象固定包含 NAME 与 DATA,顺序为先 NAME 后 DATA SERIES = [{NAME = 一季度, DATA = [10, 20, 30]}]
NAME 系列名称,取不加引号的标识符,支持中文 NAME = 产品A
DATA 系列数据点数组,仅接受数字(整数或小数) DATA = [120, 200.5, 150]

支持的图表类型(10 种):

配置项名称 描述 用法
LINE 趋势分析折线图 TYPE = LINE
COLUMN 垂直对比柱状图 TYPE = COLUMN
BAR 水平对比条形图 TYPE = BAR
BAR3D 三维条形图 TYPE = BAR3D
PIE 比例展示饼图 TYPE = PIE
AREA 累积趋势面积图 TYPE = AREA
AREA3D 三维面积图 TYPE = AREA3D
SCATTER 相关性散点图 TYPE = SCATTER
RADAR 多维比较雷达图 TYPE = RADAR
SURFACE 曲面数据图 TYPE = SURFACE
  • 完整示例:
EXPORT WITH GRAPH = {
TYPE = COLUMN,
TITLE = "季度销售统计",
CATEGORY_AXIS = 产品,
VALUE_AXIS = 销量,
CATEGORIES = ["产品A", "产品B", "产品C"],
SERIES = [
{NAME = 第一季度, DATA = [120, 200, 150]},
{NAME = 第二季度, DATA = [180, 210, 190]}
]
}
  • 作用:设置工作表页脚文本。
  • 取值说明:字符串;支持在文本中嵌入表达式进行渲染。
  • 示例:
EXPORT WITH
FOOTER="Generated by JQuickExcel",
MAPPING={"name":"Name"}

带表达式的示例:

EXPORT WITH
MAPPING={"name":"Name","gender":"Gender"},
TRANSFORM={"gender":trans(${dict},${gender})},
FOOTER="Generated by JQuickExcel on ${current_date()}"
EXPORT WITH
SHEET="学生表",
HEADER=true,
MAPPING={
"id":"主键",
"name":"姓名",
"gender":"性别",
"age":"年龄",
"enrollmentDate":"入学时间",
"className":"班级"
},
FORMAT={
"enrollmentDate":"yyyy-MM-dd"
},
TRANSFORM={
"gender":trans(${dict},${gender}),
"age":add(${age},1),
"enrollmentDate":dateFormat(${enrollmentDate},'yyyy-MM-dd')
},
FORMULAS={
D5:'ABS(D2)'
},
STYLE={
ROW 1:{
fontName:Arial,
fontHeightInPoints:12,
italic:true,
color:yellow,
bold:true
}
},
MERGE:{
ROWS 1..1,
COLS A..D WITH FIRST
},
GRAPH={
TYPE=PIE,
TITLE="Revenue",
CATEGORIES=["Q1"],
SERIES=[{NAME="2023",DATA=[1]}]
},
FOOTER="Generated by JQuickExcel"

除声明式参数外,JQuick-Excel 内置 42 套导出模板(主题)。每套主题统一控制表头行、奇偶斑马数据行、大标题行、公式单元格与页脚的配色、字体与边框风格;主题编码由枚举 com.github.paohaijiao.theme.enums.JExcelThemeType 定义。主题为导出能力,导入不受影响。

主题不通过 DSL 关键字设置,而是在规则解析之后、创建导出处理器之前,通过以下两种方式指定。

方式一:在 JExcelExportModel 上调用 setTheme。

package org.example;
import com.github.paohaijiao.executor.JQuickExcelCommonExportExecutor;
import com.github.paohaijiao.handler.JExcelExportHandler;
import com.github.paohaijiao.model.JExcelExportModel;
import com.github.paohaijiao.statement.JQuickRow;
import org.apache.poi.ss.usermodel.Workbook;
import java.io.FileOutputStream;
import java.util.List;
/**
* 导出主题使用示例 / export theme example.
*/
public class ThemeExportDemo {
public static void main(String[] args) throws Exception {
// 1. 解析声明式导出规则
String rule = "EXPORT WITH HEADER=true";
JQuickExcelCommonExportExecutor executor = new JQuickExcelCommonExportExecutor();
JExcelExportModel model = (JExcelExportModel) executor.execute(rule);
// 2. 创建导出处理器之前设置主题编码(不区分大小写)
model.setTheme("jade");
// 3. 准备业务数据行
List<JQuickRow> rows = prepareRows();
// 4. 创建处理器并导出
JExcelExportHandler handler = new JExcelExportHandler(model, rows);
Workbook workbook = handler.getWorkBook();
try (FileOutputStream os = new FileOutputStream("d:/test/theme.xlsx")) {
workbook.write(os);
}
}
private static List<JQuickRow> prepareRows() {
// 省略:按业务数据组装 JQuickRow 列表
return java.util.Collections.emptyList();
}
}

方式二:使用 XML 代理工厂时,通过构造器第一个参数传入主题编码。

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.OutputStream;
import java.util.List;
// rows 为业务数据行,os 为导出目标流
JQuickExcelExportXmlParseFactory factory =
new JQuickExcelExportXmlParseFactory("jade", rows, os);
// 后续按 XML 代理方式 createApi(...) 并调用接口方法即可

42 个内置主题编码与名称如下:

配置项名称 描述 用法
default 经典皇家蓝(默认主题) setTheme("default")
oceanBlue 海洋蓝 setTheme("oceanBlue")
forestGreen 森林绿 setTheme("forestGreen")
sunsetOrange 落日橙 setTheme("sunsetOrange")
lavenderPurple 薰衣草紫 setTheme("lavenderPurple")
sakuraPink 樱花粉 setTheme("sakuraPink")
midnightDark 午夜深色 setTheme("midnightDark")
minimalistGrey 极简灰 setTheme("minimalistGrey")
vintageSepia 复古棕 setTheme("vintageSepia")
tropicalTeal 热带青 setTheme("tropicalTeal")
royalGold 皇家金 setTheme("royalGold")
mintFresh 清新薄荷 setTheme("mintFresh")
amber 琥珀 setTheme("amber")
amethyst 紫水晶 setTheme("amethyst")
azure 蔚蓝 setTheme("azure")
charcoal 炭灰 setTheme("charcoal")
coral 珊瑚 setTheme("coral")
crimsonRed 深红 setTheme("crimsonRed")
cyan 青色 setTheme("cyan")
emerald 祖母绿 setTheme("emerald")
espresso 浓缩咖啡 setTheme("espresso")
indigo 靛蓝 setTheme("indigo")
mustard 芥末黄 setTheme("mustard")
navyBlue 海军蓝 setTheme("navyBlue")
oliveGreen 橄榄绿 setTheme("oliveGreen")
peach 蜜桃 setTheme("peach")
sage 鼠尾草绿 setTheme("sage")
skyBlue 天空蓝 setTheme("skyBlue")
steelBlue 钢蓝 setTheme("steelBlue")
terracotta 陶土 setTheme("terracotta")
turquoise 绿松石 setTheme("turquoise")
wineRed 酒红 setTheme("wineRed")
plum 紫梅 setTheme("plum")
slate 板岩灰 setTheme("slate")
pearl 珍珠白 setTheme("pearl")
bronze 青铜 setTheme("bronze")
roseQuartz 粉晶 setTheme("roseQuartz")
jade 翡翠绿 setTheme("jade")
champagne 香槟金 setTheme("champagne")
denim 牛仔蓝 setTheme("denim")
mahogany 红木 setTheme("mahogany")
periwinkle 长春花蓝 setTheme("periwinkle")

下表按色系分组展示 42 套主题的实际导出效果,每行给出主题编码(code)、主题名称与预览图,便于直接对照选用。

经典与灰色系

配置项名称(code) 描述(名称) 用法(预览)
default 经典皇家蓝 default 经典皇家蓝
minimalistGrey 极简灰 minimalistGrey 极简灰
slate 板岩灰 slate 板岩灰

深色与海洋蓝系

配置项名称(code) 描述(名称) 用法(预览)
charcoal 炭灰 charcoal 炭灰
navyBlue 海军蓝 navyBlue 海军蓝
oceanBlue 海洋蓝 oceanBlue 海洋蓝

浅蓝与钢蓝系

配置项名称(code) 描述(名称) 用法(预览)
skyBlue 天空蓝 skyBlue 天空蓝
azure 蔚蓝 azure 蔚蓝
steelBlue 钢蓝 steelBlue 钢蓝

牛仔蓝与靛蓝系

配置项名称(code) 描述(名称) 用法(预览)
denim 牛仔蓝 denim 牛仔蓝
indigo 靛蓝 indigo 靛蓝
periwinkle 长春花蓝 periwinkle 长春花蓝

森林绿与翡翠绿系

配置项名称(code) 描述(名称) 用法(预览)
forestGreen 森林绿 forestGreen 森林绿
emerald 祖母绿 emerald 祖母绿
jade 翡翠绿 jade 翡翠绿

薄荷与低饱和绿系

配置项名称(code) 描述(名称) 用法(预览)
mintFresh 清新薄荷 mintFresh 清新薄荷
sage 鼠尾草绿 sage 鼠尾草绿
oliveGreen 橄榄绿 oliveGreen 橄榄绿

青绿与青色系

配置项名称(code) 描述(名称) 用法(预览)
tropicalTeal 热带青 tropicalTeal 热带青
turquoise 绿松石 turquoise 绿松石
cyan 青色 cyan 青色

橙色与蜜桃系

配置项名称(code) 描述(名称) 用法(预览)
sunsetOrange 落日橙 sunsetOrange 落日橙
coral 珊瑚 coral 珊瑚
peach 蜜桃 peach 蜜桃

深红、酒红与樱花系

配置项名称(code) 描述(名称) 用法(预览)
crimsonRed 深红 crimsonRed 深红
wineRed 酒红 wineRed 酒红
sakuraPink 樱花粉 sakuraPink 樱花粉

粉色与紫色系

配置项名称(code) 描述(名称) 用法(预览)
roseQuartz 粉晶 roseQuartz 粉晶
lavenderPurple 薰衣草紫 lavenderPurple 薰衣草紫
amethyst 紫水晶 amethyst 紫水晶

紫梅与金色系

配置项名称(code) 描述(名称) 用法(预览)
plum 紫梅 plum 紫梅
royalGold 皇家金 royalGold 皇家金
champagne 香槟金 champagne 香槟金

琥珀、芥末与青铜系

配置项名称(code) 描述(名称) 用法(预览)
amber 琥珀 amber 琥珀
mustard 芥末黄 mustard 芥末黄
bronze 青铜 bronze 青铜

复古棕与深棕系

配置项名称(code) 描述(名称) 用法(预览)
vintageSepia 复古棕 vintageSepia 复古棕
espresso 浓缩咖啡 espresso 浓缩咖啡
mahogany 红木 mahogany 红木

陶土、深色与珍珠系

配置项名称(code) 描述(名称) 用法(预览)
terracotta 陶土 terracotta 陶土
midnightDark 午夜深色 midnightDark 午夜深色
pearl 珍珠白 pearl 珍珠白

以下示例使用统一的 XML 定义方式。每个示例给出对应的 jquick-excel.xml 定义与完整 Java 代码。

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.SheetNameService">
<excel name="readByName" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
SHEET="Students",
HEADER=true
]]>
</excel>
</excels>

服务接口与调用代码:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface SheetNameService {
List<JQuickRow> readByName(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class ReadByNameDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = ReadByNameDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
SheetNameService service = factory.createApi(SheetNameService.class);
List<JQuickRow> rows = service.readByName("field", "value");
rows.forEach(row -> System.out.println(row));
}
}
}

SHEET 使用整数位置时从 1 开始计数,下例读取第一张工作表:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.SheetIndexService">
<excel name="readByIndex" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
SHEET=1,
HEADER=true
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface SheetIndexService {
List<JQuickRow> readByIndex(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class ReadByIndexDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = ReadByIndexDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
SheetIndexService service = factory.createApi(SheetIndexService.class);
List<JQuickRow> rows = service.readByIndex("field", "value");
System.out.println("读取行数:" + rows.size());
}
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.TransformService">
<excel name="importWithTransform" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
MAPPING={
"学号": "no",
"姓名": "name",
"性别": "sex",
"出生日期": "birthday"
},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface TransformService {
List<JQuickRow> importWithTransform(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.param.JContext;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TransformImportDemo {
public static void main(String[] args) throws Exception {
Map<String, Object> gender = new HashMap<>();
gender.put("男", "1");
gender.put("女", "2");
JContext context = new JContext();
context.put("dict", gender);
try (InputStream input = TransformImportDemo.class.getClassLoader()
.getResourceAsStream("templates/student.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(context, input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
TransformService service = factory.createApi(TransformService.class);
List<JQuickRow> rows = service.importWithTransform("field", "value");
rows.forEach(row -> {
// JQuickRow 提供按字段名取值的类型化方法
System.out.println(row.getString("name") + " / " + row.getString("sex"));
});
}
}
}

JQuickRow 还提供 toBean(Class) 与静态方法 toBeanList(rows, Class),可直接将结果行转换为 Java Bean:

import com.github.paohaijiao.statement.JQuickRow;
import java.util.List;
public class RowConvertDemo {
public static List<StudentModel> toBeans(List<JQuickRow> rows) {
return JQuickRow.toBeanList(rows, StudentModel.class);
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.ValidationService">
<excel name="importWithValidation" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
VALIDATION={
ROW 2..100:{
integer{required:true,msg:'年龄必须是整数'}
},
C2:C100:{
email{required:true,msg:'邮箱格式无效'}
}
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface ValidationService {
List<JQuickRow> importWithValidation(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class ValidationImportDemo {
public static void main(String[] args) {
try (InputStream input = ValidationImportDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
ValidationService service = factory.createApi(ValidationService.class);
List<JQuickRow> rows = service.importWithValidation("field", "value");
System.out.println("校验通过,导入行数:" + rows.size());
} catch (Exception exception) {
// 校验不通过时,异常信息为 DSL 中配置的 msg
System.err.println("数据校验失败:" + exception.getMessage());
}
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.BasicExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"id":"ID","name":"Name","age":"Age"}
]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface BasicExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class BasicExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", "1001");
row.put("name", "Alice");
row.put("age", 20);
data.add(row);
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
BasicExportService service = factory.createApi(BasicExportService.class);
service.export("field", "value");
}
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StyleExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="Report",
HEADER=true,
MAPPING={"id":"ID","name":"Name","amount":"Amount"},
FORMAT={"amount":"currency"},
STYLE={
ROW 1:{
fontName:Arial,
bold:true,
color:yellow
},
A2:C100:{
alignment:center,
borderBottom:thin
}
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface StyleExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class StyleExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", "1001");
row.put("name", "Alice");
row.put("amount", 128.5);
data.add(row);
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("report.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StyleExportService service = factory.createApi(StyleExportService.class);
service.export("field", "value");
}
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.MergeExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
HEADER=true,
MERGE:{
ROWS 1..1,
COLS A..F WITH FIRST
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface MergeExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MergeExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("merged.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
MergeExportService service = factory.createApi(MergeExportService.class);
service.export("field", "value");
}
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.FormulaExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
FORMULAS={
D5:'SUM(D2:D4)',
E5:'ROUND(AVERAGE(D2:D4),2)',
F5:'RANK(D2,D2:D4)'
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface FormulaExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FormulaExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("formula.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
FormulaExportService service = factory.createApi(FormulaExportService.class);
service.export("field", "value");
}
}
}

XML 定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.ChartExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH GRAPH = {
TYPE = PIE,
TITLE = "Market Share",
CATEGORIES = ["A", "B", "C"],
SERIES = [{NAME = "Share", DATA = [38.5, 22.3, 15.7]}]
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface ChartExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ChartExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("chart.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
ChartExportService service = factory.createApi(ChartExportService.class);
service.export("field", "value");
}
}
}

导出到Http响应(浏览器下载,SpringBoot)

Section titled “导出到Http响应(浏览器下载,SpringBoot)”

框架不依赖 Spring,因此在 Spring Boot 中将响应流(HttpServletResponse.getOutputStream())作为导出目标即可:

package com.example.controller;
import com.example.WebExportService;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RestController
public class StudentExportController {
@GetMapping("/students/export")
public void exportStudents(HttpServletResponse response) throws Exception {
// 业务数据
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("学生表.xlsx", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition",
"attachment; filename*=UTF-8''" + fileName);
try (OutputStream output = response.getOutputStream()) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
WebExportService service = factory.createApi(WebExportService.class);
service.export("field", "value");
output.flush();
}
}
}
package com.example.controller;
import com.github.paohaijiao.xml.param.Param;
public interface WebExportService {
void export(@Param("field") String field, @Param("value") String value);
}

导出到本地文件 / 写入OutputStream

Section titled “导出到本地文件 / 写入OutputStream”

导出数据可写入任意 OutputStream:本地文件使用 FileOutputStream,其他场景使用对应目标的输出流。完整示例参见本文「最简导出示例」,核心代码如下:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
public class OutputTargetDemo {
// 写入本地文件
public void writeToFile(List<Map<String, Object>> data, String filePath) throws Exception {
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream(filePath)) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
buildAndExport(parser);
}
}
// 写入内存输出流(例如后续作为邮件附件、对象存储内容)
public byte[] writeToMemory(List<Map<String, Object>> data) throws Exception {
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
buildAndExport(parser);
return output.toByteArray();
}
}
private void buildAndExport(JQuickParseHandler parser) {
// 由 XML 工厂创建代理并完成导出,此处省略服务接口调用
}
}

高行数导出可通过 JQuickExcelConfig 调整流式行为,默认配置如下:

配置项 作用 默认值
streamingExportEnabled 是否允许切换 SXSSF 流式写入 true
streamingRowAccessWindowSize 内存中保留的行数 100
streamingExportThreshold 达到该行数时自动切换流式 5000
streamingCompressTempFiles 是否压缩流式临时文件 true
cellStyleCacheEnabled 是否启用单元格样式缓存(避免超出 POI 64000 样式上限) true
import com.github.paohaijiao.config.JQuickExcelConfig;
public class ConfigDemo {
public static void configure() {
JQuickExcelConfig config = JQuickExcelConfig.getInstance();
config.setStreamingExportEnabled(true)
.setStreamingRowAccessWindowSize(100)
.setStreamingExportThreshold(5000)
.setStreamingCompressTempFiles(true)
.setCellStyleCacheEnabled(true);
}
}

导入解析工厂以 InputStream 作为输入,从文件路径读取时先构造文件输入流:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
public class FilePathImportDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = new FileInputStream("D:/data/students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
FilePathImportService service = factory.createApi(FilePathImportService.class);
List<JQuickRow> rows = service.importExcel("field", "value");
System.out.println("导入行数:" + rows.size());
}
}
}

JQuickExcelImportXmlParseFactory 直接接收任意来源的 InputStream,例如类路径资源、网络流或上传文件流:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class StreamImportDemo {
public List<JQuickRow> doImport(InputStream input) throws Exception {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StreamImportService service = factory.createApi(StreamImportService.class);
return service.importExcel("field", "value");
}
}

大文件导入建议使用 JExcelImportHandler 的批量读取能力,按固定批大小消费,避免一次性保留全部行:

import com.github.paohaijiao.handler.JExcelImportHandler;
import com.github.paohaijiao.model.JExcelImportModel;
import com.github.paohaijiao.statement.JQuickRow;
import java.io.InputStream;
import java.util.List;
public class BatchImportDemo {
public static void main(String[] args) throws Exception {
JExcelImportModel model = new JExcelImportModel();
try (InputStream input = new java.io.FileInputStream("D:/data/big.xlsx")) {
JExcelImportHandler handler = new JExcelImportHandler(input);
// 每 5000 行消费一批;返回值为实际读取的总行数
int total = handler.importDataInBatch(model, 5000, (List<JQuickRow> batch) -> {
process(batch);
// 返回 true 继续读取下一批
return true;
});
System.out.println("总计读取:" + total);
}
}
private static void process(List<JQuickRow> batch) {
// 在此处理当前批次数据
}
}

JQuickExcelConfig 中与导入相关的默认配置:

配置项 作用 默认值
bigFileImportEnabled 是否启用 OPCPackage 方式解析以降低峰值内存 true
importBatchThreshold 建议分批处理的行数阈值(供使用方判断,不强制) 20000

导出到本地文件时使用 FileOutputStream 作为输出目标,完整示例见「最简导出示例」。核心写法:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
public class WriteFileDemo {
public void write(List<JQuickRow> rows, String filePath) throws Exception {
try (OutputStream output = new FileOutputStream(filePath)) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
// 使用 JQuickXmlFactory 创建代理并调用导出方法
}
}
}

导出目标可以是任意 OutputStream:HTTP 响应流、ByteArrayOutputStream、对象存储上传流等。传入工厂的流由调用方负责关闭,建议使用 try-with-resources:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.OutputStream;
import java.util.List;
public class WriteStreamDemo {
public void write(List<JQuickRow> rows, OutputStream target) {
// JAssert 要求 rows 与 OutputStream 均不能为 null
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, target);
// 使用 JQuickXmlFactory 创建代理并调用导出方法
}
}

README 未提供与 EasyExcel、原生 POI 的逐项对比表。以下仅为代码层面可确认的事实:

  • JQuick-Excel 基于 Apache POI 封装,不改变 POI 的工作簿模型,普通写入与 SXSSF 流式写入均由 POI 提供。
  • JQuick-Excel 的主要增量是 XML 服务定义 + 声明式 DSL:工作表选择、表头映射、值转换、校验、公式、样式、合并、图表与页脚均以参数形式集中描述。
  • 框架不提供监听器模式;大文件导入通过分批读取的方式消费。
  • 框架不强制依赖 Spring,通过工厂创建服务代理即可在任意 Java 程序中使用。

除上述事实外的逐项能力对比,README 暂未提供。

1. XML 定义文件应该放在哪里?

放在类路径(resources 目录)中,创建 JQuickXmlFactory 时以该文件名作为参数,例如 new JQuickXmlFactory(parser, "jquick-excel.xml")。

2. DSL 中的关键字和标点可以改写吗?

不可以。IMPORT、EXPORT、WITH 等关键字以及花括号、冒号、逗号、范围语法需要准确保留,否则解析不通过。

3. 高行数导出时如何避免内存占用过高?

JQuickExcelConfig 默认允许在数据量达到 5000 行时自动切换 SXSSF 流式写入,内存中默认保留 100 行,可按需要调整。

4. 大文件如何导入?

使用 JExcelImportHandler.importDataInBatch(model, pageSize, consumer) 按批消费;配置中推荐的分批阈值默认为 20000 行。

5. 为什么需要 JContext?

TRANSFORM 表达式中以 ${key} 引用的外部值(例如数据字典)通过 JContext 提供;${field} 则读取当前行字段值。

6. 单元格样式数量有上限吗?

POI 存在单元格样式数量上限。框架默认启用样式缓存复用样式,建议保持 cellStyleCacheEnabled=true。

7. TRANSFORM 函数和 FORMULAS 公式有什么区别?

TRANSFORM 函数是 Java 表达式,在导入导出过程中直接计算;FORMULAS 写入的是 Excel 公式,由表格软件打开时计算。两者即使同名也不可互换。

8. 图表的数据需要注意什么?

每个系列的 DATA 数组应与 CATEGORIES 一一对应(长度对齐),否则图表分类与数据会错位。

9. 流需要手动关闭吗?

需要。输入流与输出流由调用方提供并负责关闭,推荐使用 try-with-resources。

10. 如何使用内置导出主题?

创建导出解析工厂时可传入主题编码(例如 new JQuickExcelExportXmlParseFactory("jade", context, rows, output)),框架内置 42 套主题;不传主题时使用默认样式。

以下为代码与 README 可确认的限制:

  • 当前版本不提供监听器模式,也不提供全局转换器注册能力;值转换通过 DSL TRANSFORM 参数与可选的 SPI 函数依赖完成。
  • 代理模式依赖类路径中的 XML 定义文件;XML 中 <excel name> 必须与服务接口方法名一致,namespace 必须与服务接口全限定名一致。
  • 通过 XML 代理导入时,若执行过程中发生 IO 异常,异常会被捕获并记录,方法最终返回空集合。
  • jquick-transform-function 提供的 248 个 TRANSFORM 函数只有在显式引入该依赖后才可用。
  • 工作表索引使用 1 基计数;按名称指定的目标工作表不存在时会自动新建。
  • 仅支持 xls 与 xlsx 两种工作簿格式。

JQuick-Excel 基于 Apache License, Version 2.0 发布。

Copyright (c) [2025-2099] Martin ([email protected])
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

每组按每 10 个 Demo 一批分页,用「上一批 / 下一批」切换。