ExcelImportFactory.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. package cn.exlive.exbooter.excel;
  2. import cn.hutool.core.util.ReflectUtil;
  3. import com.alibaba.excel.EasyExcel;
  4. import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
  5. import com.alibaba.fastjson.JSONObject;
  6. import com.fasterxml.jackson.annotation.JsonFormat;
  7. import com.fasterxml.jackson.core.JsonProcessingException;
  8. import com.fasterxml.jackson.databind.ObjectMapper;
  9. import com.opencsv.CSVReader;
  10. import com.opencsv.CSVReaderBuilder;
  11. import lombok.extern.slf4j.Slf4j;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.apache.commons.lang3.time.FastDateFormat;
  14. import java.io.BufferedReader;
  15. import java.io.File;
  16. import java.io.FileInputStream;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.InputStreamReader;
  20. import java.lang.reflect.Field;
  21. import java.nio.charset.StandardCharsets;
  22. import java.text.ParseException;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.Date;
  26. import java.util.HashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. /**
  30. * <pre>
  31. *
  32. * Created by zhenqin.
  33. * User: zhenqin
  34. * Date: 2025/2/13
  35. * Time: 09:17
  36. * Vendor: yiidata.com
  37. *
  38. * </pre>
  39. *
  40. * @author zhenqin
  41. */
  42. @Slf4j
  43. public abstract class ExcelImportFactory<T> {
  44. /**
  45. * 返回表头
  46. * @return
  47. */
  48. public abstract Map<Integer, String> getHeaderMap();
  49. /**
  50. * 返回数据
  51. *
  52. * @param processCallback 进度回调. 返回 0-100 之间的进度条
  53. * @return
  54. */
  55. public abstract List<T> getData(ProcessCallback processCallback);
  56. /**
  57. * 获取 实例
  58. * @param excelFile excel or csv 文件
  59. * @param headerFieldMapping 文件头对应的映射
  60. * @return
  61. */
  62. public static <T> ExcelImportFactory<T> newInstance(File excelFile,
  63. Map<String, String> headerFieldMapping,
  64. Class<T> clazz) {
  65. try (InputStream in = new FileInputStream(excelFile);) {
  66. if (excelFile.getName().endsWith(".csv")) {
  67. return new CsvFactory(in, headerFieldMapping);
  68. }
  69. return newInstance(in, headerFieldMapping, clazz);
  70. } catch (IOException e) {
  71. throw new IllegalStateException("文件无法读取", e);
  72. }
  73. }
  74. /**
  75. * 获取 实例
  76. * @param in excel 文件
  77. * @param headerFieldMapping 文件头对应的映射
  78. * @return
  79. */
  80. public static <T> ExcelImportFactory<T> newInstance(InputStream in,
  81. Map<String, String> headerFieldMapping,
  82. Class<T> clazz) {
  83. ExcelFactory factory = new ExcelFactory(in, headerFieldMapping, clazz);
  84. return factory;
  85. }
  86. /**
  87. * Excel 数据导入
  88. */
  89. static class ExcelFactory<T> extends ExcelImportFactory<T> {
  90. /**
  91. * 转换的目标 类型
  92. */
  93. final Class<T> clazz;
  94. /**
  95. * 表头
  96. */
  97. final Map<String, String> headerAndFieldMapping;
  98. /**
  99. * 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
  100. */
  101. final OnceReadListener readListener;
  102. public ExcelFactory(InputStream in, Map<String, String> headerFieldMapping, Class<T> clazz) {
  103. this.clazz = clazz;
  104. this.readListener = new OnceReadListener(headerFieldMapping);
  105. // 这里直接赋值,OnceReadListener 中可能会修改字段
  106. this.headerAndFieldMapping = headerFieldMapping;
  107. final ExcelReaderSheetBuilder sheet = EasyExcel.read(in, readListener).sheet();
  108. sheet.doRead();
  109. }
  110. /**
  111. * 返回表头
  112. * @return
  113. */
  114. @Override
  115. public Map<Integer, String> getHeaderMap() {
  116. return readListener.getHeadMap();
  117. }
  118. @Override
  119. public List<T> getData(ProcessCallback processCallback) {
  120. final Map<Integer, String> headMap = readListener.getHeadMap();
  121. if(headMap == null) {
  122. throw new IllegalStateException("无法读取 Excel 表头,非法的 Excel 表格。");
  123. }
  124. final Map<Integer, String> headFieldMap = new HashMap<>();
  125. for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
  126. final String value = headerAndFieldMapping.get(StringUtils.trimToEmpty(entry.getValue()));
  127. if(StringUtils.isBlank(value)) {
  128. continue;
  129. }
  130. headFieldMap.put(entry.getKey(), value);
  131. }
  132. log.info("header: {}", headMap);
  133. // 通过反射,获取 Bean 的字段和字段类型的映射,方便日期类型转换
  134. Map<String, Field> objClassFields = new HashMap<>();
  135. final Field[] fields = ReflectUtil.getFields(clazz);
  136. for (Field field : fields) {
  137. objClassFields.put(field.getName(), field);
  138. }
  139. ObjectMapper mapper = new ObjectMapper();
  140. final List<Object> list = readListener.getList();
  141. final List<T> fxaPeople = new ArrayList<>(list.size());
  142. final int total = list.size();
  143. int index = 0;
  144. for (Object o : list) {
  145. index++;
  146. // 每 10 条数据,更新一次进度
  147. if (index % 10 == 0) {
  148. processCallback.process((int) (index * 100.0f / total));
  149. }
  150. Map<Integer, Object> mapData = (Map) o;
  151. JSONObject json = new JSONObject();
  152. // mapData,key 是第 N 列,value 是值
  153. for (Map.Entry<Integer, Object> entry : mapData.entrySet()) {
  154. final String fieldName = headFieldMap.get(entry.getKey());
  155. if (StringUtils.isBlank(fieldName)) {
  156. // 没有字段名称,需要舍弃的
  157. continue;
  158. }
  159. // 字段和值,放入,值没有转换,如:日期,字典码
  160. final Field field = objClassFields.get(fieldName);
  161. if(field == null || entry.getValue() == null) {
  162. // 如果字段为空,或者值为空,则写入空
  163. json.put(fieldName, entry.getValue());
  164. } else if(field.getType() == Date.class || field.getType() == java.sql.Date.class ) {
  165. // 日期类型
  166. final JsonFormat annotation = field.getAnnotation(JsonFormat.class);
  167. // System.out.println(fieldName + " " + annotation + " " + entry.getValue());
  168. if(entry.getValue() instanceof String) {
  169. try {
  170. String v = (String) entry.getValue();
  171. if (v.contains("/")) {
  172. json.put(fieldName, FastDateFormat.getInstance("yyyy/MM/dd").parse(v));
  173. } else if (annotation != null) {
  174. final String pattern = annotation.pattern();
  175. json.put(fieldName, FastDateFormat.getInstance(pattern).parse(v));
  176. }
  177. } catch (ParseException e) {
  178. throw new IllegalStateException("第 " + index + " 行,错误的日期格式:" + entry.getValue());
  179. }
  180. } else if(entry.getValue() instanceof Number) {
  181. json.put(fieldName, new Date(((Number)entry.getValue()).longValue()));
  182. }
  183. } else {
  184. json.put(fieldName, entry.getValue());
  185. }
  186. }
  187. try {
  188. T obj = mapper.readValue(json.toJSONString(), clazz);
  189. fxaPeople.add(obj);
  190. } catch (JsonProcessingException e) {
  191. throw new IllegalStateException("解析实体异常。", e);
  192. }
  193. }
  194. // 入库量
  195. log.info("excel data size: {}", fxaPeople.size());
  196. return fxaPeople;
  197. }
  198. }
  199. /**
  200. * CSV 数据导入
  201. *
  202. * @author zhenqin
  203. */
  204. static class CsvFactory extends ExcelImportFactory {
  205. /**
  206. * 前端传入的表头
  207. */
  208. final Map<String, String> headerAndFieldMapping;
  209. final InputStream in;
  210. public CsvFactory(InputStream in, Map<String, String> headerFieldMapping) {
  211. this.in = in;
  212. this.headerAndFieldMapping = headerFieldMapping;
  213. }
  214. /**
  215. * 返回表头
  216. * @return
  217. */
  218. @Override
  219. public Map<Integer, String> getHeaderMap() {
  220. return null;
  221. }
  222. /**
  223. * 返回数据
  224. *
  225. * @param processCallback 进度回调. 返回 0-100 之间的进度条
  226. * @return
  227. */
  228. @Override
  229. public List<JSONObject> getData(ProcessCallback processCallback) {
  230. try (CSVReader csvReader = new CSVReaderBuilder(
  231. new BufferedReader(
  232. new InputStreamReader(in, StandardCharsets.UTF_8))).build();) {
  233. String[] headTitle = csvReader.readNext();
  234. List<String> headList = Arrays.asList(headTitle);
  235. Map<Integer, String> headFieldMap = new HashMap<>();
  236. for (int i = 0; i < headList.size(); i++) {
  237. String titleName = headList.get(i);
  238. String value = headerAndFieldMapping.get(StringUtils.trimToEmpty(titleName));
  239. if (StringUtils.isBlank(value)) {
  240. continue;
  241. }
  242. headFieldMap.put(i, value);
  243. }
  244. log.info("header: {}", headTitle);
  245. List<String[]> dataList = csvReader.readAll();
  246. List<JSONObject> fxaPeople = new ArrayList<>(dataList.size());
  247. final int total = dataList.size();
  248. int index = 0;
  249. for (String[] data : dataList) {
  250. JSONObject json = new JSONObject();
  251. index++;
  252. // 每 10 条数据,更新一次进度
  253. if (index % 10 == 0) {
  254. processCallback.process((int) (index * 100.0f / total));
  255. }
  256. for (int i = 0; i < data.length; i++) {
  257. String fieldName = headFieldMap.get(i);
  258. String dataValue = data[i];
  259. if (StringUtils.isBlank(fieldName)) {
  260. // 没有字段名称,需要舍弃的
  261. continue;
  262. }
  263. json.put(fieldName, dataValue);
  264. }
  265. fxaPeople.add(json);
  266. }
  267. // 入库量
  268. log.info("csv data size: {}", fxaPeople.size());
  269. return fxaPeople;
  270. } catch (Exception e) {
  271. e.printStackTrace();
  272. return new ArrayList<JSONObject>();
  273. }
  274. }
  275. }
  276. }