在业务系统中,CSV 导出几乎是一个绕不开的需求。
看起来很简单,但真正做过的人一定踩过这些坑:
- Excel 打开 CSV,中文乱码
- 字段里有逗号、引号,整行错位
- 每个导出都写一套 DTO,代码重复又丑
- 工具类和业务对象强绑定,复用性极差
最近在项目中,我整理了一个通用 CSV 导出工具类,使用下来非常顺手,索性分享出来,或许也能帮到你。
一、设计目标
在写这个工具类之前,我给自己定了几个目标:
- 不依赖任何具体业务对象
- 支持任意 DTO / VO / 聚合对象
- Excel 直接打开,不出现中文乱码
- CSV 格式严格、安全,不怕特殊字符
- 使用方式要足够简单
最终的实现,也基本满足了这些要求。
二、核心设计思路
1️⃣ 使用泛型 + Function 映射行数据
核心方法签名如下:
java
public static <T> String exportCsv(
List<String> headers,
List<T> dataList,
Function<T, List<Object>> rowMapper)
这里的关键点是 Function<T, List<Object>>:
- 工具类 完全不知道 T 是什么
- 每一行如何映射,由调用方决定
- 不侵入 DTO,也不需要额外注解
这种设计方式在公共工具类中非常友好。
2️⃣ 主动写入 UTF-8 BOM,解决 Excel 中文乱码
java
sb.append("\uFEFF");
这是一个非常容易被忽略、但极其重要的细节。
在 Windows 环境下,Excel 对 UTF-8 的识别并不稳定,不写 BOM,中文大概率乱码。
写入 BOM 几乎没有成本,却能解决 90% 的问题,非常值得。
3️⃣ CSV 字段统一加双引号,彻底避免格式问题
java
return "\"" + val.replace("\"", "\"\"") + "\"";
CSV 的坑主要来自:
- 字段中包含逗号
, - 包含双引号
" - 包含换行符
这里采用的是 RFC 4180 标准做法:
- 所有字段统一加双引号
- 字段内的
"转义为""
简单、粗暴、但极其稳定。
三、完整工具类代码
java
package com.fang.industry.service.common.utils;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* CSV工具类
*/
public class CsvUtils {
/**
* 通用CSV导出方法
*
* @param headers 表头列表(中文)
* @param dataList 数据源列表
* @param rowMapper 行数据映射函数
*/
public static <T> String exportCsv(
List<String> headers,
List<T> dataList,
Function<T, List<Object>> rowMapper) {
StringBuilder sb = new StringBuilder();
// 1. 写入 BOM,防止 Excel 打开中文乱码
sb.append("\uFEFF");
// 2. 写入表头
sb.append(String.join(",", headers)).append("\n");
// 3. 写入数据行
if (dataList != null) {
for (T data : dataList) {
List<Object> fields = rowMapper.apply(data);
String rowStr = fields.stream()
.map(CsvUtils::formatCsvField)
.collect(Collectors.joining(","));
sb.append(rowStr).append("\n");
}
}
return sb.toString();
}
/**
* 格式化 CSV 字段
*/
private static String formatCsvField(Object field) {
if (field == null) {
return "";
}
String val = field.toString();
return "\"" + val.replace("\"", "\"\"") + "\"";
}
}
四、使用示例
示例 1:导出普通 DTO 列表
数据对象
java
@Data
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String remark;
}
导出 CSV
java
List<String> headers = List.of("用户ID", "姓名", "年龄", "备注");
List<User> users = List.of(
new User(1L, "张三", 18, "正常用户"),
new User(2L, "李四", 25, "包含,逗号"),
new User(3L, "王五", 30, "包含\"引号\"")
);
String csv = CsvUtils.exportCsv(
headers,
users,
user -> List.of(
user.getId(),
user.getName(),
user.getAge(),
user.getRemark()
)
);
生成的 CSV 用 Excel 打开完全正常。
示例 2:Spring Boot 接口直接下载 CSV
java
@GetMapping("/export")
public void exportCsv(HttpServletResponse response) throws IOException {
List<String> headers = List.of("ID", "姓名", "年龄");
List<User> users = userService.list();
String csv = CsvUtils.exportCsv(
headers,
users,
u -> List.of(u.getId(), u.getName(), u.getAge())
);
response.setContentType("text/csv;charset=UTF-8");
response.setHeader("Content-Disposition",
"attachment; filename=\"users.csv\"");
response.getWriter().write(csv);
}
五、适用场景 & 扩展思考
这个工具类特别适合:
- 后台管理系统 CSV 导出
- 临时分析数据导出
- 中小数据量(非百万级)场景
如果数据量特别大,可以进一步优化为:
- 使用
Writer流式写出 - 或直接对接文件下载流
六、总结
这个 CSV 工具类的核心特点是:
- 通用:不绑定任何业务对象
- 安全:彻底规避 CSV 格式坑
- 实用:Excel 打开即用,不乱码
如果你也在项目中经常写 CSV 导出,不妨试试这种写法,或许能让你的工具类库更干净一些。
如果你觉得有用,欢迎点赞、收藏。
后面如果有机会,也会继续分享一些真正来自业务一线的实战代码。