<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手把手教你簡(jiǎn)單實(shí)現(xiàn)一個(gè)導(dǎo)出文件下載進(jìn)度條

          共 19091字,需瀏覽 39分鐘

           ·

          2023-08-07 19:01

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進(jìn)!你不來,我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

          編輯:業(yè)余草

          來源:juejin.cn/post/7261741840829874235

          推薦:https://t.zsxq.com/10kIjsBVG

          自律才能自由

          前言

          今天要跟大家分享的是一個(gè)導(dǎo)出數(shù)據(jù)進(jìn)度條的簡(jiǎn)單實(shí)現(xiàn),適用場(chǎng)景用在數(shù)據(jù)量大、組織數(shù)據(jù)耗時(shí)的情況下的簡(jiǎn)單實(shí)現(xiàn)。

          設(shè)計(jì)思路

          1. 導(dǎo)出數(shù)據(jù)生成文件上傳到 OSS
          2. 導(dǎo)出數(shù)據(jù)狀態(tài)存 redis 緩存
          3. 前端發(fā)導(dǎo)出請(qǐng)求后,返回的文件 key
          4. 請(qǐng)求后端,后端查詢緩存情況返回
          5. 前端解析是否完成標(biāo)值,如果完成結(jié)束輪詢,執(zhí)行下載 get 下載,如果未完成,等待下一次輪詢

          設(shè)計(jì)時(shí)序圖

          設(shè)計(jì)時(shí)序圖

          核心代碼

          導(dǎo)出請(qǐng)求

          下載請(qǐng)求

          /**
           * 因子達(dá)標(biāo)分析匯總表導(dǎo)出
           *
           * @param airEnvQualityQueryVo 因子達(dá)標(biāo)分析匯總表導(dǎo)出
           * @return 統(tǒng)一出參
           */

          @PostMapping("/propSummaryData/export")
          @ApiOperation("因子達(dá)標(biāo)分析匯總表導(dǎo)出")
          public RestMessage propSummaryData4Export(@RequestBody AirEnvQualityQueryVo airEnvQualityQueryVo) {
              Assert.notNull(airEnvQualityQueryVo, "查詢參數(shù)不能為空");
              Assert.notNull(airEnvQualityQueryVo.getStartTime(), "開始時(shí)間不能為空");
              Assert.notNull(airEnvQualityQueryVo.getEndTime(),"結(jié)束時(shí)間不能為空");
              Assert.isTrue(StringUtils.isNotBlank(airEnvQualityQueryVo.getQueryType()),"查詢類型不能為空");
              SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
              String key = "propSummaryData:"+formatter.format(new Date());
              AsyncUtil.submitTask(key,() ->{
                  //獲取并組織excel數(shù)據(jù)
                  String url;
                  try {
                      url = airEnvironmentExportService.propSummaryData4Export(airEnvQualityQueryVo,key);
                  } catch (Exception e) {
                      throw new BusinessException(e.getMessage());
                  }
                  return url;
              });
              return RestBuilders.successBuilder().data(key).build();
          }

          「serviceImpl」

          /**
           * 因子達(dá)標(biāo)分析匯總表導(dǎo)出
           *
           * @param airEnvQualityQueryVo 因子達(dá)標(biāo)分析匯總表導(dǎo)出
           * @return 統(tǒng)一出參
           */

          @Override
          public String propSummaryData4Export(AirEnvQualityQueryVo airEnvQualityQueryVo, String key) throws IOException {
              //獲取匯聚數(shù)據(jù)
              AirEnvQualityResultOverviewVo resultOverviewVo = airEnvironmentQualityStatisticsService.getAirEnvQualityResultOverviewVo(airEnvQualityQueryVo);
              //數(shù)據(jù)轉(zhuǎn)換
              resultOverviewVo.setTqRateCompStr(rateHandlerStr(resultOverviewVo.getTqRateComp()));
              resultOverviewVo.setSqRateCompStr(rateHandlerStr(resultOverviewVo.getSqRateComp()));
              //獲取或者數(shù)據(jù)
              List<AirEnvQualityPropSummaryVo> airEnvQualityPropSummaryVos = airEnvironmentQualityStatisticsService.propSummaryData(airEnvQualityQueryVo);
              AtomicInteger done = new AtomicInteger();
              AsyncUtil.setTotal(key,airEnvQualityPropSummaryVos.size());
              airEnvQualityPropSummaryVos.forEach(vo ->{
                  //數(shù)據(jù)轉(zhuǎn)換
                  vo.setBqReachRateStr(rateHandler(vo.getBqReachRate()));
                  vo.setTqReachRateCompStr(rateHandlerStr(vo.getTqReachRateComp()));
                  vo.setSqReachRateCompStr(rateHandlerStr(vo.getSqReachRateComp()));
                  vo.setBqExceedRateStr(rateHandler(vo.getBqExceedRate()));
                  vo.setTqExceedRateCompStr(rateHandlerStr(vo.getTqExceedRateComp()));
                  vo.setSqExceedRateCompStr(rateHandlerStr(vo.getSqExceedRateComp()));
                  done.getAndIncrement();
                  AsyncUtil.setDone(key,done.get());
              });
              //組織導(dǎo)出數(shù)據(jù)
              Map<String,Object> map = new HashMap<>();
              map.put("p",resultOverviewVo);
              map.put("w",airEnvQualityPropSummaryVos);
              String url = getExcelUrl(map, "propSum.xlsx""因子分析匯總");
              return url;
          }

          核心工具類

          AsyncUtil 負(fù)責(zé)異步更新生成文件數(shù)據(jù)組織情況更新,存儲(chǔ)到緩存。

          import cn.hutool.core.collection.CollectionUtil;
          import com.easylinkin.oss.OSSBaseService;
          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          import org.springframework.beans.BeansException;
          import org.springframework.context.ApplicationContext;
          import org.springframework.context.ApplicationContextAware;
          import org.springframework.data.redis.core.RedisTemplate;
          import org.springframework.stereotype.Component;

          import java.io.BufferedInputStream;
          import java.io.ByteArrayOutputStream;
          import java.io.FileInputStream;
          import java.io.IOException;
          import java.util.ArrayList;
          import java.util.List;
          import java.util.Optional;
          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;
          import java.util.concurrent.ScheduledExecutorService;
          import java.util.concurrent.TimeUnit;
          import java.util.concurrent.atomic.AtomicReference;
          import java.util.function.Supplier;

          @Component
          public class AsyncUtil implements ApplicationContextAware {
            static Logger LOG = LoggerFactory.getLogger(AsyncUtil.class);
            public static ExecutorService executor = Executors.newFixedThreadPool(40);
            public static ScheduledExecutorService ex = Executors.newScheduledThreadPool(1);
            static List<String> keys = new ArrayList<>();
            static boolean scheduleIsStart = false;

            private static OSSBaseService ossService;

            @Override
            public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              ossService = applicationContext.getBean(OSSBaseService.class);
            }

            public static RedisTemplate<String, RedisAsyResultData> getRedisTemplate() {
              return SpringUtils.getBean("redisTemplate", RedisTemplate.class);
            }

            static void updateKeyLiveTime() {
              if (!scheduleIsStart) {
                // 更新redis中緩存的過期時(shí)間
                ex.scheduleAtFixedRate(() -> {
                  try {
                    LOG.info("----- update AsyncResult keys length:{} -----",
                        keys.size());
                    if (CollectionUtil.isNotEmpty(keys)) {
                      List<RedisAsyResultData> multiGet =
                          getRedisTemplate().opsForValue().multiGet(keys);
                      for (RedisAsyResultData result : multiGet) {
                        if (result != null) {
                          String key = result.getRedisKey();
                          getRedisTemplate()
                              .expire(key, 5, TimeUnit.MINUTES);
                        }
                      }
                    }
                  } catch (Exception e) {
                    scheduleIsStart = false;
                    LOG.error(e.getMessage(), e);
                  }
                }, 13, TimeUnit.MINUTES);
                scheduleIsStart = true;
              }
            }

            public static RedisAsyResultData submitExportTask(String key, Supplier supplier) {
              RedisAsyResultData rs = new RedisAsyResultData();
              rs.setSuccess(false);
              rs.setRedisKey(key);
              rs.setDone(0);
              rs.setTotal(100);
              setToRedis(rs, key);
              if (!keys.contains(key)) {
                keys.add(key);
              }
              String finalKey = key;
              executor.submit(() -> {
                String msg = null;
                try {
                  Object o = supplier.get();
                  rs.setData(o);
                  rs.setFlag(true);
                } catch (Exception e) {
                  rs.setFlag(false);
                  msg = e.getMessage();
                  LOG.error(e.getMessage(), e);
                }
                rs.setSuccess(true);
                rs.setDone(rs.getTotal());
                if (null != msg) {
                  rs.setError(msg);
                }
                keys.remove(finalKey);
                setToRedis(rs, finalKey);
              });
              updateKeyLiveTime();
              return rs;
            }

            /**
             * 設(shè)置進(jìn)度
             * @param key
             * @param done
             * @return
             */

            public static void setDone(String key,Integer done){
              RedisAsyResultData result = getResult(key);
              Optional.ofNullable(result).ifPresent(re -> {
                re.setDone(done);
                saveResult(key,result);
              });
            }

            /**
             * 設(shè)置總數(shù)
             * @param key
             * @param total
             * @return
             */

            public static void setTotal(String key,Integer total){
              RedisAsyResultData result = getResult(key);
              Optional.ofNullable(result).ifPresent(re -> {
                re.setTotal(total);
                saveResult(key,result);
              });
            }

            public static RedisAsyResultData submitTask(String key, Supplier supplier) {
              AtomicReference<RedisAsyResultData> rs = new AtomicReference<>(new RedisAsyResultData());
              rs.get().setSuccess(false);
              rs.get().setRedisKey(key);
              rs.get().setDone(0);
              rs.get().setTotal(100);
              setToRedis(rs.get(), key);
              if (!keys.contains(key)) {
                keys.add(key);
              }
              String finalKey = key;
              executor.submit(() -> {
                String msg = null;
                try {
                  Object o = supplier.get();
                  RedisAsyResultData result = getResult(key);
                  if (null != result){
                    rs.set(result);
                  }
                  rs.get().setData(o);
                  rs.get().setFlag(true);
                } catch (Exception e) {
                  rs.get().setFlag(false);
                  msg = e.getMessage();
                  LOG.error(e.getMessage(), e);
                }
                rs.get().setSuccess(true);

                rs.get().setDone(rs.get().getTotal());
                if (null != msg) {
                  rs.get().setError(msg);
                }
                keys.remove(finalKey);
                setToRedis(rs.get(), finalKey);
              });
              updateKeyLiveTime();
              return rs.get();
            }

            private static void setToRedis(RedisAsyResultData result, String redisKey) {
              getRedisTemplate().opsForValue().set(redisKey, result, 5, TimeUnit.MINUTES);
            }

            public static RedisAsyResultData getResult(String key) {
              RedisAsyResultData excelResult =
                  getRedisTemplate().opsForValue().get(key);
              if (null != excelResult) {
                return excelResult;
              }
              return null;
            }

            public static void saveResult(String key, RedisAsyResultData result) {
              setToRedis(result, key);
            }

            public static byte[] FileToByte(String filePath) throws Exception{
              FileInputStream fis = null;
              BufferedInputStream bis = null;
              try {
                fis = new FileInputStream(filePath);
                bis = new BufferedInputStream(fis);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int c = bis.read();
                while (c != -1) {
                  // 數(shù)據(jù)存儲(chǔ)到ByteArrayOutputStream中
                  baos.write(c);
                  c = bis.read();
                }
                fis.close();
                bis.close();
                // 轉(zhuǎn)換成二進(jìn)制
                byte[] bytes = baos.toByteArray();
                return bytes;
              }catch (Exception e){
                e.printStackTrace();
                throw e;
              }finally {
                try {
                  if (fis != null ) {
                    fis.close();
                  }
                } catch (IOException e) {
                  e.printStackTrace();
                  throw e;
                } finally {
                  try {
                    if (bis != null ) {
                      bis.close();
                    }
                  } catch (IOException e) {
                    e.printStackTrace();
                    throw e;
                  }
                }
              }
            }
          }

          查詢導(dǎo)出文件生成情況接口

          /**
          * 根據(jù)key獲取導(dǎo)出接口
          @param key
          @return
          */

          @GetMapping("getRedisResult/{key}")
          public RestMessage getRedisResult(@PathVariable String key){
          Assert.hasLength(key,"key不能為空");
            return RestBuilders.successBuilder().data(AsyncUtil.getResult(key)).build();
          }

          key 為導(dǎo)出請(qǐng)求返回的。

          效果

          效果

          前端進(jìn)度條由每一次輪詢請(qǐng)求返回的 total、done 計(jì)算。

          效果

          最后一次輪詢,判斷 flag 的值 true,或者自行判斷 total 與 done 相等,又或者判斷 data 是否又返回 url 表示是否生成完成,然后用返回的 url 進(jìn)行 get 請(qǐng)求執(zhí)行下載。

          總結(jié)

          簡(jiǎn)單的實(shí)現(xiàn)進(jìn)度條,用在數(shù)據(jù)需要長(zhǎng)時(shí)間一條條生成時(shí),看進(jìn)度條特別明顯 輪詢其實(shí)也可以用 websocket 替代(這樣可以離開頁面做其他操作,當(dāng)然這樣也是可以的,就是輪詢要做到全局請(qǐng)求了,業(yè)務(wù)模塊多的下載的時(shí)候前后端都?jí)毫ψ兇螅?這里其實(shí)還用到了 easypoi 的模板導(dǎo)出,大家可以自己看看 api。

          更多學(xué)習(xí)內(nèi)容來自如下星球,歡迎更多的人加入!

          瀏覽 1062
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人亚洲精品 | 在线观看成人自拍 | 操B在线免费视频 | 操比在线| 无遮挡WWW |