无关风月
1 天以前 25b19e60e004290531f61fdf608d1adb5e531903
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
package com.ruoyi.system.utils;
 
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.ruoyi.system.vo.system.ProjectDeptDetailsChildVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
 
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
 
/**
 * @Author: 一杯美式
 * @version: V1.0
 * @Date: 2024-04-23 10:38
 * @Package: com.victory.jfe.pdm.srm.config
 */
@Slf4j
@Service
public class ExcelPoiUtils {
    /**
     * 动态列导出
     * @param response    响应
     * @param fileName    文件名
     * @param title       标题
     * @param sheetName   sheet名称
     * @param dataList    导出数据
     * @param headerName  动态列标题
     * @param headerValue 动态列值
     * @param <T>
     * @throws Exception
     */
    public static <T> void exportExcel(HttpServletResponse response, String fileName, String title, String sheetName,
                                       List<T> dataList, String headerName, String headerValue) throws Exception {
        if (CollectionUtils.isNotEmpty(dataList)) {
            T dataInstance = dataList.get(0);
            List<ExcelExportEntity> entityList = buildExcelExportEntityList(dataInstance, headerName, "");
            List<Object> objList = convertDataListToObjList(dataList, headerName,headerValue);
            downloadExcelEntityDynamic(response, entityList, objList, fileName, title, sheetName);
        }
    }
    public static <T> void exportExcel(HttpServletResponse response, String fileName, String title, String sheetName,
                                       List<T> dataList, List<ProjectDeptDetailsChildVO> projectDeptDetailsChildVOS, String headerName, String headerValue) throws Exception {
        if (CollectionUtils.isNotEmpty(dataList)) {
            ProjectDeptDetailsChildVO dataInstance = projectDeptDetailsChildVOS.get(0);
            List<ExcelExportEntity> entityList = buildExcelExportEntityList(dataInstance, headerName, "");
            List<Object> objList = convertDataListToObjList(dataList, headerName,headerValue);
            downloadExcelEntityDynamic(response, entityList, objList, fileName, title, sheetName);
        }
    }
 
    public static <T> void exportExcel(HttpServletResponse response, String fileName, String title, String sheetName,
                                       List<T> dataList, String headerName, String headerValue, String ignoreCol) throws Exception {
        if (CollectionUtils.isNotEmpty(dataList)) {
            T dataInstance = dataList.get(0);
            List<ExcelExportEntity> entityList = buildExcelExportEntityList(dataInstance, headerName, ignoreCol);
            List<Object> objList = convertDataListToObjList(dataList, headerName,headerValue);
            downloadExcelEntityDynamic(response, entityList, objList, fileName, title, sheetName);
        }
    }
 
    /**
     * 构建Excel导出实体列表
     *
     * @param t          取数据集第一条数据 做实体列表构建
     * @param headerName 动态列标题
     * @param <T>        数据类型
     * @return Excel导出实体列表
     * @throws IllegalAccessException 如果无法访问字段
     */
    public static <T> List<ExcelExportEntity> buildExcelExportEntityList(T t, String headerName, String ignoreCol) throws IllegalAccessException {
        List<ExcelExportEntity> entityList = new ArrayList<>();
        Field[] fields = t.getClass().getDeclaredFields();
        int index = 0;
 
        for (Field field : fields) {
            field.setAccessible(true);
            Optional<Excel> excelOpt = Optional.ofNullable(field.getAnnotation(Excel.class));
            Optional<ExcelCollection> excelCollectionOpt = Optional.ofNullable(field.getAnnotation(ExcelCollection.class));
 
            if (excelOpt.isPresent()) {
                // 处理固定导出列
                if (ignoreCol.contains(field.getName())) {
                    index++;
                } else {
                    index = handleFixedExportColumn(entityList, field, excelOpt.get(), index);
                }
            } else if (excelCollectionOpt.isPresent() && List.class.isAssignableFrom(field.getType())) {
                // 处理自定义导出列
                index = handleCustomExportColumn(t, entityList, field, headerName, index);
            }
        }
 
        return entityList;
    }
 
    /**
     * 处理固定导出列
     *
     * @param entityList 实体列表
     * @param field      字段
     * @param excel      Excel注解
     * @param index      索引
     * @return 更新后的索引
     */
    private static int handleFixedExportColumn(List<ExcelExportEntity> entityList, Field field, Excel excel, int index) {
        Object name = AnnotationUtil.getAnnotationValue(field, Excel.class, "name");
        ExcelExportEntity entity = createExcelExportEntity(field, name.toString(), field.getName(), index);
        entityList.add(entity);
        return index + 1;
    }
 
    /**
     * 处理自定义导出列
     *
     * @param t          数据对象
     * @param entityList 实体列表
     * @param field      字段
     * @param headerName 动态列标题
     * @param index      索引
     * @param <T>        数据类型
     * @return 更新后的索引
     * @throws IllegalAccessException 如果无法访问字段
     */
    private static <T> int handleCustomExportColumn(T t, List<ExcelExportEntity> entityList, Field field, String headerName, int index) throws IllegalAccessException {
        List<?> dynamicColl = (List<?>) field.get(t);
        for (Object arr : dynamicColl) {
            Field[] typeFields = arr.getClass().getDeclaredFields();
            for (Field typeField : typeFields) {
                typeField.setAccessible(true);
                Excel excelItem = typeField.getAnnotation(Excel.class);
                if (excelItem != null && headerName.equals(typeField.getName())) {
                    Object value = typeField.get(arr);
                    ExcelExportEntity entity = createExcelExportEntity(typeField, value.toString(), value.toString(), index);
                    entityList.add(entity);
                    index++;
                }
            }
        }
        return index;
    }
 
    /**
     * 将数据对象列表转换为对象列表,通过异步处理每个项。
     *
     * @param dataList 需要处理的数据对象列表。
     * @param headerName 用于从对象中提取特定值的标题名称。
     * @param headerValue 用于从对象中提取特定值的标题值。
     * @param <T> 数据对象列表中的对象类型。
     * @return 表示处理过的数据对象的映射列表。
     */
    public static <T> List<Object> convertDataListToObjList(List<T> dataList, String headerName, String headerValue) {
        // 创建一个固定大小的线程池 =处理器数量
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        long start = System.currentTimeMillis();
        List<CompletableFuture<Object>> futures = new ArrayList<>();
        // 提交每个数据对象进行异步处理
        for (T data : dataList) {
            futures.add(CompletableFuture.supplyAsync(() -> processData(data, headerName, headerValue), executorService));
        }
        // 收集处理结果
        List<Object> objList = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
        // 关闭线程池
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(2, TimeUnit.MINUTES)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
 
        log.info("数据处理时间:" + (System.currentTimeMillis() - start) + "ms");
        return objList;
    }
 
    /**
     * 处理单个数据对象以提取相关字段及其值。
     *
     * @param data 要处理的数据对象。
     * @param headerName 用于从对象中提取特定值的标题名称。
     * @param headerValue 用于从对象中提取特定值的标题值。
     * @param <T> 数据对象的类型。
     * @return 表示数据对象的映射。
     */
    private static <T> Object processData(T data, String headerName, String headerValue) {
        Map<String, Object> dataMap = new HashMap<>();
        Field[] fields = ReflectUtil.getFields(data.getClass());
 
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                // 将字段名和值放入dataMap中
                dataMap.put(field.getName(), field.get(data));
                ExcelCollection excelCollection = field.getAnnotation(ExcelCollection.class);
 
                // 如果字段是ExcelCollection并且是List类型,进一步处理
                if (excelCollection != null && field.getType().getName().equals(List.class.getName())) {
                    List<?> dynamicColl = (List<?>) field.get(data);
 
                    for (Object arr : dynamicColl) {
                        String key = null;
                        String val = null;
                        Field[] typeFields = arr.getClass().getDeclaredFields();
 
                        for (Field typeField : typeFields) {
                            typeField.setAccessible(true);
                            Excel excelItem = typeField.getAnnotation(Excel.class);
                            // 根据注解提取key和value
                            if (excelItem != null) {
                                if (headerName.equals(typeField.getName())) {
                                    key = String.valueOf(typeField.get(arr));
                                } else if (headerValue.equals(typeField.getName())) {
                                    val = String.valueOf(typeField.get(arr));
                                }
                            }
                        }
                        dataMap.put(key, val);
                    }
                }
            } catch (IllegalAccessException e) {
                log.error("无法访问字段值:", e.getMessage());
                return null;
            }
        }
 
        return dataMap;
    }
 
 
    /**
     * 动态生成并下载Excel文件。
     *
     * @param response HttpServletResponse对象,用于发送响应。
     * @param entityList Excel导出实体列表。
     * @param list 数据列表。
     * @param fileName 文件名。
     * @param title 标题。
     * @param sheetName 工作表名称。
     * @throws Exception 可能抛出的异常。
     */
    public static void downloadExcelEntityDynamic(HttpServletResponse response, List<ExcelExportEntity> entityList,
                                                  List<Object> list, String fileName, String title,
                                                  String sheetName) throws Exception {
        setResponseHeadersForDownload1(response, fileName);
        ExportParams exportParams = StringUtils.hasText(title) ? new ExportParams(title, sheetName) : new ExportParams();
        if (!StringUtils.hasText(title)) {
            exportParams.setSheetName(sheetName);
        }
 
        int pageSize = 12000;
        int totalPages = (list.size() + pageSize - 1) / pageSize;
        Workbook workbook = ExcelExportUtil.exportBigExcel(exportParams, entityList, (obj, page) -> {
            if (((int) obj) < page) {
                return null;
            }
            log.info("当前查询第{}页数据", page);
            int fromIndex = (page - 1) * pageSize;
            int toIndex = Math.min(page * pageSize, list.size());
            return list.subList(fromIndex, toIndex);
        }, totalPages);
 
        try (ServletOutputStream output = response.getOutputStream();
             BufferedOutputStream bufferedOutPut = new BufferedOutputStream(output)) {
            workbook.write(bufferedOutPut);
            bufferedOutPut.flush();
        }
    }
 
    private static void setResponseHeadersForDownload1(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xls");
    }
 
 
    public static void setResponseHeadersForDownload(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "utf-8"));
        response.setContentType("application/octet-stream;charset=utf-8");
    }
 
    /**
     * 将@Excel或者@ExcelCollection修饰的字段转为ExcelExportEntity
     */
    private static ExcelExportEntity createExcelExportEntity(Field typeField, String name, String key, int index) {
        Map<String, Object> annotationValueMap = AnnotationUtil.getAnnotationValueMap(typeField, Excel.class);
        ExcelExportEntity entity = JSONObject.parseObject(JSONObject.toJSONBytes(annotationValueMap), ExcelExportEntity.class);
        // 字段名和@Excel的name一致,视为动态表头列
        entity.setName(name);
        entity.setKey(key);
        entity.setOrderNum(index);
        return entity;
    }
 
 
    private static ExcelExportEntity createExcelExportEntity1(Field typeField, String name, String key, int index) {
        ExcelExportEntity entity = new ExcelExportEntity();
        // 设置基本信息
        entity.setName(name);
        entity.setKey(key);
        entity.setOrderNum(index);
        // 设置注解中的属性
        Excel excel = typeField.getAnnotation(Excel.class);
        if (excel != null) {
            try {
                BeanUtils.copyProperties(entity, excel); // 复制注解中的属性到ExcelExportEntity对象
            } catch (Exception e) {
                log.error(e.getMessage());
                throw new RuntimeException("无法从Excel注解复制属性:", e);
            }
        }
 
        return entity;
    }
 
}