|  | @@ -1,4 +1,4 @@
 | 
	
		
			
				|  |  | -package com.chelvc.framework.export.support;
 | 
	
		
			
				|  |  | +package com.chelvc.framework.export;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import java.io.File;
 | 
	
		
			
				|  |  |  import java.io.FileOutputStream;
 | 
	
	
		
			
				|  | @@ -12,6 +12,7 @@ import java.util.Objects;
 | 
	
		
			
				|  |  |  import java.util.function.BiConsumer;
 | 
	
		
			
				|  |  |  import java.util.function.BiFunction;
 | 
	
		
			
				|  |  |  import java.util.stream.Collectors;
 | 
	
		
			
				|  |  | +import javax.annotation.PostConstruct;
 | 
	
		
			
				|  |  |  import javax.servlet.http.HttpServletResponse;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import com.chelvc.framework.base.context.ApplicationContextHolder;
 | 
	
	
		
			
				|  | @@ -21,16 +22,20 @@ 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.StringUtils;
 | 
	
		
			
				|  |  |  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.RequiredArgsConstructor;
 | 
	
		
			
				|  |  |  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;
 | 
	
		
			
				|  |  | +import org.springframework.beans.factory.annotation.Autowired;
 | 
	
		
			
				|  |  | +import org.springframework.data.redis.core.RedisTemplate;
 | 
	
		
			
				|  |  | +import org.springframework.stereotype.Component;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * 数据导出默认实现
 | 
	
	
		
			
				|  | @@ -39,12 +44,11 @@ import org.apache.poi.xssf.streaming.SXSSFWorkbook;
 | 
	
		
			
				|  |  |   * @date 2024/5/9
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  @Slf4j
 | 
	
		
			
				|  |  | +@Component
 | 
	
		
			
				|  |  | +@RequiredArgsConstructor(onConstructor = @__(@Autowired))
 | 
	
		
			
				|  |  |  public class DefaultExportHandler implements ExportHandler {
 | 
	
		
			
				|  |  |      private final ExportProperties properties;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public DefaultExportHandler(@NonNull ExportProperties properties) {
 | 
	
		
			
				|  |  | -        this.properties = properties;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    private final RedisTemplate<String, Object> redisTemplate;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * 清理导出文件
 | 
	
	
		
			
				|  | @@ -71,7 +75,7 @@ public class DefaultExportHandler implements ExportHandler {
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      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);
 | 
	
		
			
				|  |  | +        List<?> values = this.redisTemplate.opsForValue().multiGet(keys);
 | 
	
		
			
				|  |  |          if (ObjectUtils.notEmpty(values)) {
 | 
	
		
			
				|  |  |              for (int i = 0; i < values.size(); i++) {
 | 
	
		
			
				|  |  |                  if (Objects.isNull(values.get(i))) {
 | 
	
	
		
			
				|  | @@ -91,7 +95,34 @@ public class DefaultExportHandler implements ExportHandler {
 | 
	
		
			
				|  |  |          return "exporting:" + id;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    @Override
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 获取任务状态
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param id 任务ID
 | 
	
		
			
				|  |  | +     * @return 任务状态信息
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private Pair<String, Boolean> pair(Serializable id) {
 | 
	
		
			
				|  |  | +        String value = (String) this.redisTemplate.opsForValue().get(this.key(id));
 | 
	
		
			
				|  |  | +        if (StringUtils.isEmpty(value)) {
 | 
	
		
			
				|  |  | +            return null;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        String[] splits = value.split(":");
 | 
	
		
			
				|  |  | +        return Pair.of(splits[0], Boolean.valueOf(splits[1]));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 更新任务状态
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param id       任务ID
 | 
	
		
			
				|  |  | +     * @param pair     任务状态信息
 | 
	
		
			
				|  |  | +     * @param duration 有效时间
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private void pair(Serializable id, Pair<String, Boolean> pair, Duration duration) {
 | 
	
		
			
				|  |  | +        String value = pair.getLeft() + ":" + pair.getRight();
 | 
	
		
			
				|  |  | +        this.redisTemplate.opsForValue().set(this.key(id), value, duration);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @PostConstruct
 | 
	
		
			
				|  |  |      public void initialize() {
 | 
	
		
			
				|  |  |          ThreadUtils.run(() -> {
 | 
	
		
			
				|  |  |              while (!Thread.currentThread().isInterrupted()) {
 | 
	
	
		
			
				|  | @@ -107,17 +138,17 @@ public class DefaultExportHandler implements ExportHandler {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @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));
 | 
	
		
			
				|  |  | +        Boolean completed = ObjectUtils.ifNull(this.pair(id), Pair::getRight);
 | 
	
		
			
				|  |  | +        return Boolean.TRUE.equals(completed);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public void download(long id, @NonNull HttpServletResponse response) throws IOException {
 | 
	
		
			
				|  |  | -        Pair<?, ?> pair = (Pair<?, ?>) RedisContextHolder.getRedisTemplate().opsForValue().get(this.key(id));
 | 
	
		
			
				|  |  | +        Pair<String, Boolean> pair = this.pair(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);
 | 
	
		
			
				|  |  | +        String filename = ObjectUtils.ifNull(pair, Pair::getLeft);
 | 
	
		
			
				|  |  |          HttpUtils.write(response, new File(this.properties.getDirectory(), String.valueOf(id)), filename);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -129,8 +160,7 @@ public class DefaultExportHandler implements ExportHandler {
 | 
	
		
			
				|  |  |          long id = RedisContextHolder.identity();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // 初始化数据导出状态
 | 
	
		
			
				|  |  | -        String key = this.key(id);
 | 
	
		
			
				|  |  | -        RedisContextHolder.getRedisTemplate().opsForValue().set(key, Pair.of(filename, false), Duration.ofDays(1));
 | 
	
		
			
				|  |  | +        this.pair(id, Pair.of(filename, false), Duration.ofDays(1));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // 异步执行导出任务
 | 
	
		
			
				|  |  |          ThreadContextHolder.execute(() -> {
 | 
	
	
		
			
				|  | @@ -148,8 +178,7 @@ public class DefaultExportHandler implements ExportHandler {
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // 更新数据导出任务状态
 | 
	
		
			
				|  |  | -            Duration duration = Duration.ofSeconds(this.properties.getExpiration());
 | 
	
		
			
				|  |  | -            RedisContextHolder.getRedisTemplate().opsForValue().set(key, Pair.of(filename, true), duration);
 | 
	
		
			
				|  |  | +            this.pair(id, Pair.of(filename, true), Duration.ofSeconds(this.properties.getExpiration()));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // 数据导出完成监听回调
 | 
	
		
			
				|  |  |              listener.accept(id, volume);
 |