wujianwei
2025-12-26 b2ca7af3db0d1e2baf37829c33a82cd43d690751
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
package com.ruoyi.common.core.service;
 
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.file.DownloadExportUtil;
import com.ruoyi.common.utils.poi.ExcelUtil;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.Function;
 
/**
 * 通用服务
 */
@Service
public abstract class BaseService<M extends BaseMapper<T>,T> extends ServiceImpl<M, T> {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    private RedisCache redisCache;
    /**
     * 通用异步导出方法
     *
     * @param exportKey 导出任务的唯一标识
     * @param sheetName 工作表名称
     * @param dataFetcher 数据获取函数,接受分页参数返回数据列表
     */
    @Async
    public void export(Class c, String exportKey, String sheetName, Function<Integer, List<T>> dataFetcher) {
        String fileName = ExcelUtil.encodeFileName(sheetName);
 
        // 设置当前任务为“下载中”状态
        DownloadExportUtil.deleteDownloadFile(redisCache, exportKey, "0");
 
        try {
            // 执行导出并获取文件名
            fileName = exportData( c,fileName, sheetName,  dataFetcher);
            // 设置下载完成状态
            DownloadExportUtil.setDownloadFile(redisCache, exportKey, fileName);
            logger.info("Export completed for key: {}, file: {}", exportKey, fileName);
        } catch (Exception e) {
            logger.error("Export failed for key: {}, error: {}", exportKey, e.getMessage(), e);
            DownloadExportUtil.deleteDownloadFile(redisCache, exportKey, "1"); // 设置失败状态
            throw e;
        }
    }
 
    /**
     * 通用分页导出逻辑
     *
     * @param fileName  文件名
     * @param sheetName 工作表名称
     * @param dataFetcher 数据获取函数
     * @return 导出后的文件名
     */
    protected  String exportData(Class c, String fileName, String sheetName,
                                 Function<Integer, List<T>> dataFetcher) {
        ExcelUtil<T> excelUtil = new ExcelUtil<>(c);
        excelUtil.initialize(sheetName, null, Excel.Type.EXPORT);
 
        int pageNum = 1;
        boolean hasMoreData = true;
 
        while (hasMoreData) {
            List<T> pageData = dataFetcher.apply(pageNum);
 
            if (pageData != null && !pageData.isEmpty()) {
                // 导出当前页的数据
                excelUtil.exportExcel(pageData);
                pageNum++;
            } else {
                // 没有数据时退出
                hasMoreData = false;
            }
 
        }
 
        excelUtil.finishExport(fileName);
        return fileName;
    }
 
    /**
     * 导出两个不同sheet的数据到同一个Excel文件
     *
     * @param clazz1     第一个sheet对应的实体类
     * @param clazz2     第二个sheet对应的实体类
     * @param fileName   文件名
     * @param sheetName1 第一个sheet名称
     * @param sheetName2 第二个sheet名称
     * @param dataList1  第一个sheet的数据列表
     * @param dataList2  第二个sheet的数据列表
     * @return 导出后的文件名
     */
    protected <T1, T2> String exportMultiSheetData(Class<T1> clazz1, Class<T2> clazz2, 
                                                   String fileName, String sheetName1, String sheetName2,
                                                   List<T1> dataList1, List<T2> dataList2) {
        
        // 创建第一个sheet的ExcelUtil并导出数据
        ExcelUtil<T1> excelUtil1 = new ExcelUtil<>(clazz1);
        excelUtil1.initialize(sheetName1, null, Excel.Type.EXPORT);
        
        if (dataList1 != null && !dataList1.isEmpty()) {
            excelUtil1.exportExcel(dataList1);
        }
        
        // 使用ExcelUtil的finishExport方法导出到临时文件
        String tempFileName = "temp_" + System.currentTimeMillis() + ".xlsx";
        excelUtil1.finishExport(tempFileName);
        
        try {
            // 读取临时文件并添加第二个sheet
            FileInputStream fis = new FileInputStream(getAbsoluteFile(tempFileName));
            SXSSFWorkbook workbook = new SXSSFWorkbook(new org.apache.poi.xssf.usermodel.XSSFWorkbook(fis));
            fis.close();
            
            // 创建第二个sheet
            Sheet sheet2 = workbook.createSheet(sheetName2);
            
            // 使用ExcelUtil的方式导出第二个sheet的数据
            if (dataList2 != null && !dataList2.isEmpty()) {
                // 创建第二个sheet的ExcelUtil用于生成表头
                ExcelUtil<T2> excelUtil2 = new ExcelUtil<>(clazz2);
                excelUtil2.initialize(sheetName2, null, Excel.Type.EXPORT);
                
                // 获取表头样式信息
                SXSSFWorkbook tempWorkbook = getWorkbookFromExcelUtil(excelUtil2);
                Sheet tempSheet = tempWorkbook.getSheetAt(0);
                Row headerRow = tempSheet.getRow(1); // 表头通常在第二行
                
                // 复制表头到第二个sheet
                if (headerRow != null) {
                    Row newHeaderRow = sheet2.createRow(0);
                    copyRow(headerRow, newHeaderRow);
                }
                
                // 导出数据到第二个sheet
                int startRow = 1;
                for (T2 data : dataList2) {
                    Row dataRow = sheet2.createRow(startRow++);
                    exportDataRow(clazz2, data, dataRow);
                }
                
                tempWorkbook.close();
            }
            
            // 完成导出
            finishMultiSheetExport(workbook, fileName);
            
            // 删除临时文件
            new File(getAbsoluteFile(tempFileName)).delete();
            
        } catch (Exception e) {
            logger.error("Failed to export multi-sheet data: {}", e.getMessage(), e);
            throw new RuntimeException("多sheet导出失败", e);
        }
        
        return fileName;
    }
 
    /**
     * 从ExcelUtil中获取工作簿对象
     */
    private <T> SXSSFWorkbook getWorkbookFromExcelUtil(ExcelUtil<T> excelUtil) {
        try {
            Field workbookField = ExcelUtil.class.getDeclaredField("workbook");
            workbookField.setAccessible(true);
            return (SXSSFWorkbook) workbookField.get(excelUtil);
        } catch (Exception e) {
            logger.error("Failed to get workbook from ExcelUtil: {}", e.getMessage(), e);
            throw new RuntimeException("获取工作簿失败", e);
        }
    }
 
    /**
     * 将数据导出到指定的sheet
     */
    private <T> void exportDataToSheet(Class<T> clazz, Sheet sheet, List<T> dataList) {
        try {
            // 创建临时ExcelUtil用于生成表头
            ExcelUtil<T> tempUtil = new ExcelUtil<>(clazz);
            tempUtil.initialize("temp", null, Excel.Type.EXPORT);
            
            // 获取表头行
            SXSSFWorkbook tempWorkbook = getWorkbookFromExcelUtil(tempUtil);
            Sheet tempSheet = tempWorkbook.getSheetAt(0);
            Row headerRow = tempSheet.getRow(1); // 表头通常在第二行
            
            // 复制表头到目标sheet
            if (headerRow != null) {
                Row newHeaderRow = sheet.createRow(0);
                copyRow(headerRow, newHeaderRow);
            }
            
            // 导出数据
            if (dataList != null && !dataList.isEmpty()) {
                int startRow = 1; // 数据从第二行开始
                for (T data : dataList) {
                    Row dataRow = sheet.createRow(startRow++);
                    exportDataRow(clazz, data, dataRow);
                }
            }
            
            tempWorkbook.close();
        } catch (Exception e) {
            logger.error("Failed to export data to sheet: {}", e.getMessage(), e);
            throw new RuntimeException("导出数据到sheet失败", e);
        }
    }
 
    /**
     * 复制行数据
     */
    private void copyRow(Row sourceRow, Row targetRow) {
        if (sourceRow == null || targetRow == null) return;
        
        for (int i = 0; i < sourceRow.getLastCellNum(); i++) {
            Cell sourceCell = sourceRow.getCell(i);
            Cell targetCell = targetRow.createCell(i);
            
            if (sourceCell != null) {
                // 复制单元格样式
                targetCell.setCellStyle(sourceCell.getCellStyle());
                
                // 复制单元格值
                switch (sourceCell.getCellType()) {
                    case STRING:
                        targetCell.setCellValue(sourceCell.getStringCellValue());
                        break;
                    case NUMERIC:
                        targetCell.setCellValue(sourceCell.getNumericCellValue());
                        break;
                    case BOOLEAN:
                        targetCell.setCellValue(sourceCell.getBooleanCellValue());
                        break;
                    case FORMULA:
                        targetCell.setCellFormula(sourceCell.getCellFormula());
                        break;
                    default:
                        targetCell.setCellValue("");
                }
            }
        }
    }
 
    /**
     * 导出数据行
     */
    private <T> void exportDataRow(Class<T> clazz, T data, Row row) {
        try {
            ExcelUtil<T> tempUtil = new ExcelUtil<>(clazz);
            tempUtil.initialize("temp", null, Excel.Type.EXPORT);
            
            // 获取字段信息
            Field[] fields = clazz.getDeclaredFields();
            int columnIndex = 0;
            
            for (Field field : fields) {
                field.setAccessible(true);
                Excel excel = field.getAnnotation(Excel.class);
                
                if (excel != null && excel.isExport()) {
                    Cell cell = row.createCell(columnIndex++);
                    Object value = field.get(data);
                    
                    if (value != null) {
                        cell.setCellValue(value.toString());
                    } else {
                        cell.setCellValue("");
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Failed to export data row: {}", e.getMessage(), e);
        }
    }
 
    /**
     * 完成多sheet导出
     */
    private void finishMultiSheetExport(SXSSFWorkbook workbook, String fileName) {
        try (OutputStream out = Files.newOutputStream(Paths.get(getAbsoluteFile(fileName)))) {
            workbook.write(out);
        } catch (Exception e) {
            logger.error("Failed to finish multi-sheet export: {}", e.getMessage(), e);
            throw new RuntimeException("多sheet导出失败", e);
        } finally {
            try {
                workbook.close();
            } catch (Exception e) {
                logger.error("Failed to close workbook: {}", e.getMessage(), e);
            }
        }
    }
 
    /**
     * 获取文件的绝对路径
     */
    private String getAbsoluteFile(String fileName) {
        String downloadPath = RuoYiConfig.getDownloadPath();
        File file = new File(downloadPath + fileName);
        if (!file.exists()) {
            file.getParentFile().mkdirs();
        }
        return file.getAbsolutePath();
    }
 
 
}