目录
- 一. 需求
- 二. `List<Entity>`转`List<List<String>>`
- 2.1 实体类
- 2.2 转换
- 三. 上传csv文件转`List<Map>`
- 3.1 csv文件
- 3.2 前台
- 3.3 实体类
- 3.4 转换
- 3.5 效果
一. 需求
🤔项目中遇到了两个需求
1.查询数据库,得到List<Entity>
这种数据类型,然后需要将List<Entity>
转换为List<List<String>>
之后,调用共通方法下载CSV数据。
2.前台上传csv文件到后台,后台需要将csv文件处理为List<Map>
这种数据类型,然后遍历List插入数据库。
二. List<Entity>
转List<List<String>>
2.1 实体类
- 在类的内部定义了一个
@excludeCSV
注解,用于标识指定的属性不进行转换
import lombok.Builder;
import lombok.Data;
import java.lang.annotation.*;
@Data
@Builder
public class Category {
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface excludeCSV {
}
private String id;
@excludeCSV
private String password;
private String parentValid;
private String parentId;
private String name;
private String updateTime;
}
2.2 转换
- 要点就是反射,因为实体类的属性都是private,所以需要使用 .setAccessible(true) 使其强制可访问。
- 因为不确定List中的类型是什么,因此使用 List<?>。
- 部分属性不需要转换,使用自定义注解
@excludeCSV
来标识。
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Field;
import java.util.*;
@Controller
@RequestMapping("/test33")
public class Test33Controller implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 模拟从数据库查询到的List<Entity>数据
List<Category> categoryList = List.of(
Category.builder().id("1").name("张三").parentId("11").updateTime("2023").build(),
Category.builder().id("2").name("李四").parentId("22").updateTime("2024").build(),
Category.builder().id("3").name("王五").parentId("33").updateTime("2025").build()
);
// 进行转换
List<List<String>> csvDataList = this.handleData(categoryList);
System.out.println(csvDataList);
/*
[
[1, , 11, 张三, 2023],
[2, , 22, 李四, 2024],
[3, , 33, 王五, 2025]
]
*/
}
public List<List<String>> handleData(List<?> dataList) throws IllegalAccessException {
List<List<String>> strList = new ArrayList<>();
List<String> valueList;
Field[] fields;
for (Object data : dataList) {
valueList = new ArrayList<>();
// 获取所有修饰符的属性
fields = data.getClass().getDeclaredFields();
for (Field field : fields) {
// 如果包含指定的注解,则该csv项目不处理
if (field.isAnnotationPresent(Category.excludeCSV.class)) {
continue;
}
// 使该属性强制可访问
field.setAccessible(true);
// 获取属性值
valueList.add(Optional.ofNullable(field.get(data)).orElse("").toString());
}
strList.add(valueList);
}
return strList;
}
}
三. 上传csv文件转List<Map>
3.1 csv文件
"ID","姓名","年龄","地址"
"1","张三",18,"山东省"
"2","李四",19,"山西省"
3.2 前台
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="file" id="csv" accept=".csv" />
<button id="btn">上传文件</button>
</body>
<script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script>
$(function() {
bindEvent();
});
function bindEvent() {
$("#btn").click(() => {
const formData = new FormData();
formData.append("multipartFile", $("#csv").get(0).files[0]);
$.ajax({
url: `/test33/fileUpload`,
type: 'POST',
processData: false,
contentType: false,
data: formData,
success: function (data, status, xhr) {
console.log(data);
}
});
});
}
</script>
</html>
3.3 实体类
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class Test33Form {
// 用来接收前台传入的csv文件
private MultipartFile multipartFile;
}
3.4 转换
- 因为上传的不是json数据,因此使用Form接收的时候不需要加
@RequestBody
。 - 后台接收到的文件类型为
MultipartFile
,需要将其转换为File
后读取其中的内容。 - 可以使用
File.createTempFile()
创建临时文件,使用完之后需要删除,否则占用磁盘空间。 - 可以使用
Files.readAllLines()
读取全部的文本文件数据。
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.*;
@Controller
@RequestMapping("/test33")
public class Test33Controller {
private static final String CHARSET = "UTF-8";
// CSV列的字段名映射
private Map<Integer, String> csvColumnMap = new HashMap<>(){{
put(0, "id");
put(1, "name");
put(2, "age");
put(3, "address");
}};
@PostMapping("/fileUpload")
public ResponseEntity<Void> fileUpload(Test33Form form) throws IOException {
// 获取前台上传的csv
MultipartFile multipartFile = form.getMultipartFile();
// 将csv文件临时存储到本地
String originalFilename = multipartFile.getOriginalFilename();
// 获取文件名以及文件名的后缀
String fileName = StringUtils.stripFilenameExtension(originalFilename);
String filenameExtension = StringUtils.getFilenameExtension(originalFilename);
// 创建临时文件
File tempFile = File.createTempFile(fileName, filenameExtension);
multipartFile.transferTo(tempFile);
// 指定通过 UTF-8 编码读取CSV中的数据; 如果读取的是日语文件,通常格式为 Shift_JIS
List<String> csvList = Files.readAllLines(tempFile.toPath(), Charset.forName(CHARSET));
// 删除csv文件中的第一行的标题
csvList.remove(0);
// 删除临时文件
Files.delete(tempFile.toPath());
List<Map<String, Object>> csvMapList = new ArrayList<>();
for (String csvRow : csvList) {
Map<String, Object> csvMap = new HashMap<>();
List<String> csvItemList = Arrays.asList(csvRow.split(","));
for (int i = 0; i < csvItemList.size(); i++) {
csvMap.put(csvColumnMap.get(i), csvItemList.get(i));
}
csvMapList.add(csvMap);
}
System.out.println(csvMapList);
// 响应给前台
return ResponseEntity.noContent().build();
}
}