|
@@ -1,7 +1,9 @@
|
|
|
-package com.chelvc.framework.export.support;
|
|
|
+package com.chelvc.framework.download.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;
|
|
@@ -17,14 +19,15 @@ import com.chelvc.framework.base.context.ThreadContextHolder;
|
|
|
import com.chelvc.framework.base.util.HttpUtils;
|
|
|
import com.chelvc.framework.common.function.Adapter;
|
|
|
import com.chelvc.framework.common.function.Executor;
|
|
|
+import com.chelvc.framework.common.function.OutputAcceptor;
|
|
|
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.Exporting;
|
|
|
-import com.chelvc.framework.export.config.ExportProperties;
|
|
|
+import com.chelvc.framework.download.Download;
|
|
|
+import com.chelvc.framework.download.DownloadHandler;
|
|
|
+import com.chelvc.framework.download.config.DownloadProperties;
|
|
|
import com.chelvc.framework.redis.context.RedisContextHolder;
|
|
|
import com.google.common.collect.Lists;
|
|
|
import lombok.NonNull;
|
|
@@ -36,26 +39,26 @@ import org.springframework.context.ApplicationListener;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
|
|
|
/**
|
|
|
- * 数据导出默认实现
|
|
|
+ * 数据下载处理器默认实现
|
|
|
*
|
|
|
* @author Woody
|
|
|
* @date 2024/5/9
|
|
|
*/
|
|
|
@Slf4j
|
|
|
-public class DefaultExportHandler implements ExportHandler, ApplicationListener<ApplicationStartedEvent> {
|
|
|
+public class DefaultDownloadHandler implements DownloadHandler, ApplicationListener<ApplicationStartedEvent> {
|
|
|
private final String directory;
|
|
|
private final Duration duration;
|
|
|
private final RedisTemplate<String, Object> redisTemplate;
|
|
|
|
|
|
- public DefaultExportHandler(@NonNull RedisTemplate<String, Object> redisTemplate,
|
|
|
- @NonNull ExportProperties properties) {
|
|
|
+ public DefaultDownloadHandler(@NonNull DownloadProperties properties,
|
|
|
+ @NonNull RedisTemplate<String, Object> redisTemplate) {
|
|
|
this.directory = properties.getDirectory();
|
|
|
this.duration = Duration.ofSeconds(properties.getExpiration());
|
|
|
this.redisTemplate = redisTemplate;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 清理导出文件
|
|
|
+ * 清理下载文件
|
|
|
*/
|
|
|
private void clear() {
|
|
|
int batch = 100;
|
|
@@ -73,7 +76,7 @@ public class DefaultExportHandler implements ExportHandler, ApplicationListener<
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 清理导出文件
|
|
|
+ * 清理下载文件
|
|
|
*
|
|
|
* @param files 文件列表
|
|
|
*/
|
|
@@ -96,41 +99,41 @@ public class DefaultExportHandler implements ExportHandler, ApplicationListener<
|
|
|
* @return 任务标识
|
|
|
*/
|
|
|
private String key(Serializable id) {
|
|
|
- return "exporting:" + id;
|
|
|
+ return "download:" + id;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取导出任务信息
|
|
|
+ * 获取下载任务信息
|
|
|
*
|
|
|
- * @param id 导出任务ID
|
|
|
- * @return 导出任务信息
|
|
|
+ * @param id 下载任务ID
|
|
|
+ * @return 下载任务信息
|
|
|
*/
|
|
|
- private Exporting exporting(Serializable id) {
|
|
|
- return (Exporting) this.redisTemplate.opsForValue().get(this.key(id));
|
|
|
+ private Download download(Serializable id) {
|
|
|
+ return (Download) this.redisTemplate.opsForValue().get(this.key(id));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 设置导出任务信息
|
|
|
+ * 处理下载任务信息
|
|
|
*
|
|
|
- * @param id 导出任务ID
|
|
|
+ * @param id 任务ID
|
|
|
* @param filename 文件名称
|
|
|
- * @param status 导出状态
|
|
|
+ * @param status 任务状态
|
|
|
* @param duration 任务有效期
|
|
|
*/
|
|
|
- private void exporting(Serializable id, String filename, Exporting.Status status, Duration duration) {
|
|
|
- Exporting exporting = Exporting.builder().filename(filename).status(status).build();
|
|
|
- this.redisTemplate.opsForValue().set(this.key(id), exporting, duration);
|
|
|
+ private void processing(Serializable id, String filename, Download.Status status, Duration duration) {
|
|
|
+ Download download = Download.builder().filename(filename).status(status).build();
|
|
|
+ this.redisTemplate.opsForValue().set(this.key(id), download, duration);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void onApplicationEvent(ApplicationStartedEvent event) {
|
|
|
- // 初始化导出文件清理线程
|
|
|
+ // 初始化下载文件清理线程
|
|
|
ThreadUtils.run(() -> {
|
|
|
while (!Thread.currentThread().isInterrupted()) {
|
|
|
try {
|
|
|
- RedisContextHolder.tryLockAround("exporting:clear:lock", (Executor) this::clear);
|
|
|
+ RedisContextHolder.tryLockAround("download:clear:lock", (Executor) this::clear);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("Export file clear failed", e);
|
|
|
+ log.error("Download temp file clear failed", e);
|
|
|
}
|
|
|
ThreadUtils.sleep(this.duration.toMillis());
|
|
|
}
|
|
@@ -138,69 +141,80 @@ public class DefaultExportHandler implements ExportHandler, ApplicationListener<
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Exporting.Status status(long id) {
|
|
|
- return ObjectUtils.ifNull(this.exporting(id), Exporting::getStatus);
|
|
|
+ public Download.Status status(long id) {
|
|
|
+ return ObjectUtils.ifNull(this.download(id), Download::getStatus);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void save(long id, @NonNull Workbook workbook) throws IOException {
|
|
|
- ExcelUtils.save(workbook, new File(this.directory, String.valueOf(id)));
|
|
|
+ public void write(long id, @NonNull Workbook workbook) throws IOException {
|
|
|
+ this.write(id, output -> {
|
|
|
+ workbook.write(output);
|
|
|
+ workbook.close();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(long id, @NonNull OutputAcceptor acceptor) throws IOException {
|
|
|
+ File file = new File(this.directory, String.valueOf(id));
|
|
|
+ try (OutputStream output = new FileOutputStream(file)) {
|
|
|
+ acceptor.accept(output);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void download(long id, @NonNull HttpServletResponse response) throws IOException {
|
|
|
- Exporting exporting = this.exporting(id);
|
|
|
- AssertUtils.available(exporting, () -> ApplicationContextHolder.getMessage("Export.Expired"));
|
|
|
- boolean completed = ObjectUtils.ifNull(exporting, Exporting::getStatus) == Exporting.Status.COMPLETED;
|
|
|
- AssertUtils.available(completed, () -> ApplicationContextHolder.getMessage("Export.Uncompleted"));
|
|
|
- String filename = ObjectUtils.ifNull(exporting, Exporting::getFilename);
|
|
|
+ Download download = this.download(id);
|
|
|
+ AssertUtils.available(download, () -> ApplicationContextHolder.getMessage("Download.Expired"));
|
|
|
+ boolean completed = ObjectUtils.ifNull(download, Download::getStatus) == Download.Status.COMPLETED;
|
|
|
+ AssertUtils.available(completed, () -> ApplicationContextHolder.getMessage("Download.Uncompleted"));
|
|
|
+ String filename = ObjectUtils.ifNull(download, Download::getFilename);
|
|
|
HttpUtils.write(response, new File(this.directory, String.valueOf(id)), filename);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public long export(@NonNull String filename, @NonNull Adapter<Long, Integer> adapter,
|
|
|
- @NonNull BiConsumer<Long, Integer> listener) {
|
|
|
- // 生成导出任务ID
|
|
|
+ public long async(@NonNull String filename, @NonNull Adapter<Long, Integer> adapter,
|
|
|
+ @NonNull BiConsumer<Long, Integer> listener) {
|
|
|
+ // 生成任务ID
|
|
|
long id = RedisContextHolder.identity();
|
|
|
|
|
|
- // 初始化数据导出状态
|
|
|
- this.exporting(id, filename, Exporting.Status.RUNNING, Duration.ofDays(1));
|
|
|
+ // 初始化任务状态
|
|
|
+ this.processing(id, filename, Download.Status.RUNNING, Duration.ofDays(1));
|
|
|
|
|
|
- // 异步执行导出任务
|
|
|
+ // 异步执行任务处理
|
|
|
ThreadContextHolder.run(() -> {
|
|
|
int volume;
|
|
|
try {
|
|
|
- // 导出任务处理回调
|
|
|
+ // 任务处理回调
|
|
|
volume = adapter.apply(id);
|
|
|
|
|
|
- // 更新导出任务完成状态
|
|
|
- this.exporting(id, filename, Exporting.Status.COMPLETED, this.duration);
|
|
|
+ // 更新任务完成状态
|
|
|
+ this.processing(id, filename, Download.Status.COMPLETED, this.duration);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("Export execute failed", e);
|
|
|
+ log.error("Download execute failed", e);
|
|
|
|
|
|
- // 更新导出任务失败状态
|
|
|
- this.exporting(id, filename, Exporting.Status.FAILED, this.duration);
|
|
|
+ // 更新任务失败状态
|
|
|
+ this.processing(id, filename, Download.Status.FAILED, this.duration);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 数据导出完成监听回调
|
|
|
+ // 任务完成监听回调
|
|
|
try {
|
|
|
listener.accept(id, volume);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("Export listening failed", e);
|
|
|
+ log.error("Download listening failed", e);
|
|
|
}
|
|
|
});
|
|
|
return id;
|
|
|
}
|
|
|
|
|
|
@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) {
|
|
|
- return this.export(filename, id -> {
|
|
|
+ public <T> long async(@NonNull String filename, @NonNull BiConsumer<Long, Integer> listener,
|
|
|
+ @NonNull BiFunction<Integer, Integer, Collection<T>> provider,
|
|
|
+ @NonNull ExcelUtils.Writer<T> writer, @NonNull String... titles) {
|
|
|
+ return this.async(filename, id -> {
|
|
|
Workbook workbook = new SXSSFWorkbook();
|
|
|
int volume = ExcelUtils.write(workbook, provider, writer, titles);
|
|
|
- this.save(id, workbook);
|
|
|
+ this.write(id, workbook);
|
|
|
return volume;
|
|
|
}, listener);
|
|
|
}
|