|
@@ -0,0 +1,159 @@
|
|
|
|
+package com.chelvc.framework.export.support;
|
|
|
|
+
|
|
|
|
+import java.io.File;
|
|
|
|
+import java.io.FileOutputStream;
|
|
|
|
+import java.io.IOException;
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
+import java.io.Serializable;
|
|
|
|
+import java.time.Duration;
|
|
|
|
+import java.util.Collection;
|
|
|
|
+import java.util.List;
|
|
|
|
+import java.util.Objects;
|
|
|
|
+import java.util.function.BiConsumer;
|
|
|
|
+import java.util.function.BiFunction;
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
+
|
|
|
|
+import com.chelvc.framework.base.context.ApplicationContextHolder;
|
|
|
|
+import com.chelvc.framework.base.context.ThreadContextHolder;
|
|
|
|
+import com.chelvc.framework.base.util.HttpUtils;
|
|
|
|
+import com.chelvc.framework.common.util.AssertUtils;
|
|
|
|
+import com.chelvc.framework.common.util.ExcelUtils;
|
|
|
|
+import com.chelvc.framework.common.util.FileUtils;
|
|
|
|
+import com.chelvc.framework.common.util.ObjectUtils;
|
|
|
|
+import com.chelvc.framework.common.util.ThreadUtils;
|
|
|
|
+import com.chelvc.framework.export.ExportHandler;
|
|
|
|
+import com.chelvc.framework.export.config.ExportProperties;
|
|
|
|
+import com.chelvc.framework.redis.context.RedisContextHolder;
|
|
|
|
+import com.google.common.collect.Lists;
|
|
|
|
+import lombok.NonNull;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import org.apache.commons.lang3.tuple.Pair;
|
|
|
|
+import org.apache.poi.ss.usermodel.Workbook;
|
|
|
|
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 数据导出默认实现
|
|
|
|
+ *
|
|
|
|
+ * @author Woody
|
|
|
|
+ * @date 2024/5/9
|
|
|
|
+ */
|
|
|
|
+@Slf4j
|
|
|
|
+public class DefaultExportHandler implements ExportHandler {
|
|
|
|
+ private final ExportProperties properties;
|
|
|
|
+
|
|
|
|
+ public DefaultExportHandler(@NonNull ExportProperties properties) {
|
|
|
|
+ this.properties = properties;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 清理导出文件
|
|
|
|
+ */
|
|
|
|
+ private void clear() {
|
|
|
|
+ int batch = 100;
|
|
|
|
+ List<File> files = Lists.newArrayListWithCapacity(batch);
|
|
|
|
+ FileUtils.files(this.properties.getDirectory(), file -> {
|
|
|
|
+ files.add(file);
|
|
|
|
+ if (files.size() == batch) {
|
|
|
|
+ this.clear(files);
|
|
|
|
+ files.clear();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ if (ObjectUtils.notEmpty(files)) {
|
|
|
|
+ this.clear(files);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 清理导出文件
|
|
|
|
+ *
|
|
|
|
+ * @param files 文件列表
|
|
|
|
+ */
|
|
|
|
+ private void clear(List<File> files) {
|
|
|
|
+ List<String> keys = files.stream().map(File::getName).map(this::key).collect(Collectors.toList());
|
|
|
|
+ List<?> values = RedisContextHolder.getRedisTemplate().opsForValue().multiGet(keys);
|
|
|
|
+ if (ObjectUtils.notEmpty(values)) {
|
|
|
|
+ for (int i = 0; i < values.size(); i++) {
|
|
|
|
+ if (Objects.isNull(values.get(i))) {
|
|
|
|
+ FileUtils.delete(files.get(i));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建任务标识
|
|
|
|
+ *
|
|
|
|
+ * @param id 任务ID
|
|
|
|
+ * @return 任务标识
|
|
|
|
+ */
|
|
|
|
+ private String key(Serializable id) {
|
|
|
|
+ return "exporting:" + id;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void initialize() {
|
|
|
|
+ ThreadUtils.run(() -> {
|
|
|
|
+ while (!Thread.currentThread().isInterrupted()) {
|
|
|
|
+ try {
|
|
|
|
+ this.clear();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ log.error("Export file clear failed", e);
|
|
|
|
+ }
|
|
|
|
+ ThreadUtils.sleep(60 * 1000);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public boolean isCompleted(long id) {
|
|
|
|
+ Pair<?, ?> pair = (Pair<?, ?>) RedisContextHolder.getRedisTemplate().opsForValue().get(this.key(id));
|
|
|
|
+ return Boolean.TRUE.equals(ObjectUtils.ifNull(pair, Pair::getRight));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void download(long id, @NonNull HttpServletResponse response) throws IOException {
|
|
|
|
+ Pair<?, ?> pair = (Pair<?, ?>) RedisContextHolder.getRedisTemplate().opsForValue().get(this.key(id));
|
|
|
|
+ AssertUtils.available(pair, () -> ApplicationContextHolder.getMessage("Export.Expired"));
|
|
|
|
+ boolean completed = Boolean.TRUE.equals(ObjectUtils.ifNull(pair, Pair::getRight));
|
|
|
|
+ AssertUtils.available(completed, () -> ApplicationContextHolder.getMessage("Export.Uncompleted"));
|
|
|
|
+ String filename = (String) ObjectUtils.ifNull(pair, Pair::getLeft);
|
|
|
|
+ HttpUtils.write(response, new File(this.properties.getDirectory(), String.valueOf(id)), filename);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public <T> long export(@NonNull String filename, @NonNull BiConsumer<Long, Integer> listener,
|
|
|
|
+ @NonNull BiFunction<Integer, Integer, Collection<T>> provider,
|
|
|
|
+ @NonNull ExcelUtils.Writer<T> writer, @NonNull String... titles) {
|
|
|
|
+ // 生成导出任务ID
|
|
|
|
+ long id = RedisContextHolder.identity();
|
|
|
|
+
|
|
|
|
+ // 初始化数据导出状态
|
|
|
|
+ String key = this.key(id);
|
|
|
|
+ RedisContextHolder.getRedisTemplate().opsForValue().set(key, Pair.of(filename, false), Duration.ofDays(1));
|
|
|
|
+
|
|
|
|
+ // 异步执行导出任务
|
|
|
|
+ ThreadContextHolder.execute(() -> {
|
|
|
|
+ // 将数据写入工作薄
|
|
|
|
+ Workbook workbook = new SXSSFWorkbook();
|
|
|
|
+ int volume = ExcelUtils.write(workbook, provider, writer, titles);
|
|
|
|
+
|
|
|
|
+ // 保存导出文件
|
|
|
|
+ File file = new File(this.properties.getDirectory(), String.valueOf(id));
|
|
|
|
+ try (OutputStream output = new FileOutputStream(file)) {
|
|
|
|
+ workbook.write(output);
|
|
|
|
+ workbook.close();
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ throw new RuntimeException(e);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新数据导出任务状态
|
|
|
|
+ Duration duration = Duration.ofSeconds(this.properties.getExpiration());
|
|
|
|
+ RedisContextHolder.getRedisTemplate().opsForValue().set(key, Pair.of(filename, true), duration);
|
|
|
|
+
|
|
|
|
+ // 数据导出完成监听回调
|
|
|
|
+ listener.accept(id, volume);
|
|
|
|
+ });
|
|
|
|
+ return id;
|
|
|
|
+ }
|
|
|
|
+}
|